Reproduction du Jeu de la pastèque sur navigateur avec Matter.js

Tags :
  • POK
  • 2023-2024
  • temps 3
  • Jeu
  • JavaScript
  • Frontend
  • Matter.js
  • Gravité
Auteurs :
  • Lucie Le Boursicaud

Dans ce POK je vais utiliser Matter.js pour recréer le "Jeu de la pastèque" devenu viral grace à TikTok.

Niveau intermédiare

Etre à l'aise avec JavaScript.

Introduction

Le "Jeu de la pastèque" ou Suika Game est un jeu vidéo japonais qui est sorti en décembre 2021 au Japon. Il est diffusé au reste du monde en octobre 2023 et devient un véritable phénomène très relié par les réseaux sociaux notamment par TikTok.

But du jeu

Le joueur doit remplir une jarre avec des fruits, à chaque fois qu'il en jette un, il gagne des point. L'objectif est de déverser un maximum de fruits afin d'obtenir un meilleur score. Mais lorsque deux fruits identiques entrent en contact, ils fusionnent pour donner un fruit plus gros. Le plus petit fuits est une myrtille et le plus gros une pastèque (d'où le nom du jeu). Dès lors qu'un fruit dépasse de la jarre alors la partie est terminé. Ce jeu s'est beaucoup inspiré du mécanisme de 2048 qui a été un gros succès il y a déjà quelques années. Voici une capture d'écran du jeu qui est disponible sur téléphone :

Objectif du POK

L'objectif ici est de reproduire ce jeu sur un navigateur en déplaçant le fruit avec les flèche de l'ordinateur et en le lachant avec la barre espace.

Sprint 1

Objectifs du premier sprint

1. Découvrir rapidement Matters.js

Matter.js est une bibliothèque JavaScript open-source qui permet la création de simulations physiques en 2D. Il s'agit d'une bibliothèque assez populaire dans le domaine du développement de jeux car elle est facile à utiliser et est très performante. Elle permet de simuler des objets, des collisions, des forces et la gravité, ce qui va être utile dans la création du jeu. Je me suis rendue sur une documentation pour comprendre comment fonctionne cette bibliothèque. J'ai donc regardé une première vidéo Introduction to Matter.js et la deuxième Introduction to Matter.js Continued.

2. Créer l'environnement de code

Je me crée un dossier Pasteque et j'initialise mon projet avec :

npm init 

J'ajoute la ligne "type" : "module", je crée un fichier index.js et index.html et j'installe Express et Matter via les commandes :

npm add --save express
npm install matter-js 

J'ajoute un fichier main.js dans lequel je vais coder les différentes fonctions pour le jeu.

J'ai eu beaucoup de difficultés en suivant cette méthode dont une que j'ai pas réussi à corriger :

J'ai donc utilisé Vite pour créer mon projet (ce qu'un des tutos Matter.js utilisé comme environnement) et cela à très bien marché en suivant la documentation.

3. Créer la boite du jeu

Maintenant que j'ai mon environnement je peux commencer à coder le jeu. La première chose à créer est donc une boite, pour ça j'ai juste suivi la documentation qui explique comment ajouter des corps aux mondes ce qui m'a donné cela :

Code associé

import {Engine, Render, Runner} from "matter-js";

const engine = Engine.create();
const render = Render.create({
    engine,
    element: document.body,
    options: {
        wireframes:false,
        background: "#E1FAD7",
        width: 620,
        height: 850,
    },
});

const world = engine.world;

const sol = Bodies.rectangle(115,820,620,20,{
    isStatic:true,
    render: {
        fillStyle: "#9EA1D4",
    }
});

const murgauche = Bodies.rectangle(15,470,20,700,{
    isStatic:true,
    render: {
        fillStyle: "#9EA1D4",
    }
});

const murdroite = Bodies.rectangle(410,470,20,700,{
    isStatic:true,
    render: {
        fillStyle: "#9EA1D4",
    }
});

World.add(world, [sol,murgauche,murdroite]);

Render.run(render);
Runner.run(engine);

4. Créer un rond qui tombe

De la même façon j'ai juste suivi la documentation. Et j'ai obtenu cela :

Code associé

import {Engine, Render, Runner} from "matter-js";

const engine = Engine.create();
const render = Render.create({
    engine,
    element: document.body,
    options: {
        wireframes:false,
        background: "#E1FAD7",
        width: 620,
        height: 850,
    },
});

const world = engine.world;

const sol = Bodies.rectangle(115,820,620,20,{
    isStatic:true,
    render: {
        fillStyle: "#9EA1D4",
    }
});

const murgauche = Bodies.rectangle(15,470,20,700,{
    isStatic:true,
    render: {
        fillStyle: "#9EA1D4",
    }
});

const murdroite = Bodies.rectangle(410,470,20,700,{
    isStatic:true,
    render: {
        fillStyle: "#9EA1D4",
    }
});

World.add(world, [sol,murgauche,murdroite]);

Render.run(render);
Runner.run(engine);

function ajouterFruit(){
    const body = Bodies.circle(300, 150,30, {
        render : {
            fillStyle: "green",
        },
    });
    World.add(world,body);
};

ajouterFruit();

5. Le rond qui tombe ne doit pas tomber de suite

Ici le but est de comprendre comment faire pour que le rond ne tombe pas avant que j'appuie sur la barre espace. Après avoir chercher un peu, il faut utiliser le booléen isSleeping qui permet de mettre le corps en attente et on pourra le réveiller par la suite.

Code associé

import {Engine, Render, Runner} from "matter-js";

const engine = Engine.create();
const render = Render.create({
    engine,
    element: document.body,
    options: {
        wireframes:false,
        background: "#E1FAD7",
        width: 620,
        height: 850,
    },
});

const world = engine.world;

const sol = Bodies.rectangle(115,820,620,20,{
    isStatic:true,
    render: {
        fillStyle: "#9EA1D4",
    }
});

const murgauche = Bodies.rectangle(15,470,20,700,{
    isStatic:true,
    render: {
        fillStyle: "#9EA1D4",
    }
});

const murdroite = Bodies.rectangle(410,470,20,700,{
    isStatic:true,
    render: {
        fillStyle: "#9EA1D4",
    }
});

World.add(world, [sol,murgauche,murdroite]);

Render.run(render);
Runner.run(engine);

function ajouterFruit(){
    const body = Bodies.circle(300, 150,30, {
        isSleeping: true,
        render : {
            fillStyle: "green",
        },
    });
    World.add(world,body);
};

ajouterFruit();

6. Faire bouger le rond avec les flèches

Maintenant que le rond reste endormi il faut pouvoir utiliser les flèches de notre clavier pour le faire bouger. Pour cela j'utilise des écouteurs d'évements onkeydown et en fonction du code de la touche sur laquelle j'appuie je ne vais pas faire la même action donc j'utilise un switch sur event.code. Afin de récupérer mon élement rond (qui sera un fruit par la suite) j'ajoute une ligne dans le code de la fonction ajouterFruit() qui affecte à la variable monFruit le body de l'élément en question. Voilà où j'en suis :

function ajouterFruit(){
    const body = Bodies.circle(300, 150,30, {
        isSleeping: true,
        render : {
            fillStyle: "green",
        },
    });
    monFruit = body;
    World.add(world,body);
};

window.onkeydown = (event) => {
  switch(event.code){
    case "ArrowLeft":
      
    case "ArrowRight":

  }
  }

Maintenant je veux pouvoir bouger mon élement. Pour ça on va devoir importer Body dans cette ligne ci :

import {Engine, Render, Runner, Bodies, World, Body} from 'matter-js';

et on va utiliser la méthode setPosition() comme ci dessous.

function ajouterFruit(){
    const body = Bodies.circle(300, 150,30, {
        isSleeping: true,
        render : {
            fillStyle: "green",
        },
    });
    monFruit = body;
    World.add(world,body);
};

window.onkeydown = (event) => {
  switch(event.code){
    case "ArrowLeft":
      Body.setPosition(currentFruit, {x: monFruit.position.x - 1, y:monFruit.position.y,
      });
    case "ArrowRight":
      Body.setPosition(currentFruit, {x: monFruit.position.x + 1, y:monFruit.position.y,
      });
  }
  }

Le soucis est que le mouvement de notre fruit est très lent, et si j'augmente en passant de 1 à 10 par exemple, le mouvement est saccadé et je ne peux pas faire tomber mon fruit avec précision. J'ai beaucoup cherché à résoudre ce problème pour trouver et mettre en place la solution : utiliser des intervalles. Ca permet d'automatiser la mise à jour de la position pour qu'elle se fasse toutes les temps milisecondes.


let interval = null;

window.onkeydown = (event) => {
  switch(event.code){
    case "ArrowLeft":
      if(interval) return;
      interval= setInterval(() => {
        Body.setPosition(currentFruit, {x: monFruit.position.x - 1, y:monFruit.position.y,
      });
      },5)
    case "ArrowRight":
      if(internal) return;
      interval= setInterval(() => {
        Body.setPosition(currentFruit, {x: monFruit.position.x - 1, y:monFruit.position.y,
      });
      },5)
  }
  }

window.onkeyup = (event) => {
  switch(event.code){
    case "ArrowLeft":
    case "ArrowRight":
      clearInterval(interval);
      interval = null;
  }
}

Le problème c'est que je peux déplacer le fruit à l'extérieur des murs ce qui n'est pas envisageable. Il faut donc vérifier que notre fruit ne se situe pas au bord de la boite si l'on veut le déplacer. Pour ça j'ai utilisé un if() sur la position de monFruit.

Code associé à la fonction finale

window.onkeydown = (event) => {
    switch (event.code) {
        case "ArrowDown":
            if(interval) return ; 
            interval = setInterval(() => {
                if(monFruit.position.x - 20 > 25)
            Body.setPosition(monFruit, {
                x: monFruit.position.x - 1, 
                y: monFruit.position.y,
            });
            }, 5);
            break;
        case "ArrowRight":
            if(interval) return ; 
            interval = setInterval(() => {
                if(monFruit.position.x + 20 <400)
            Body.setPosition(monFruit, {
                x: monFruit.position.x + 1, 
                y: monFruit.position.y,
            });
            }, 5);
            break;
    }
};

window.onkeyup = (event) => {
    switch (event.code) {
        case "ArrowDown":
        case "ArrowRight":
            clearInterval(interval);
            interval = null;
    }
};

Alors notre fruit est bloqué sur les deux côté du mur :

7. Faire tomber le fruit avec la barre espace

L'ojectif est clair : lorsque l'on appuie sur la barre espace, le fruit "se réveille" et tombe. Pour ça on utilise encore l'écouteur d'évenement onkeydown et on va devoir importer Sleeping depuis la bibliothèque matter.js.

import {Engine, Render, Runner, Bodies, World, Body, Sleeping} from 'matter-js';

On va donc ajouter le cas où l'on appuie sur la touche espace dans notre écouteur, et passer le booléen isSleeping à la valeur false avec la méthode set().

window.onkeydown = (event) => {
    switch (event.code) {
        case "ArrowDown":
            if(interval) return ; 
            interval = setInterval(() => {
                if(monFruit.position.x - 20 > 25)
            Body.setPosition(monFruit, {
                x: monFruit.position.x - 1, 
                y: monFruit.position.y,
            });
            }, 5);
            break;
        case "ArrowRight":
            if(interval) return ; 
            interval = setInterval(() => {
                if(monFruit.position.x + 20 <400)
            Body.setPosition(monFruit, {
                x: monFruit.position.x + 1, 
                y: monFruit.position.y,
            });
            }, 5);
            break;
        case "Space":
            Sleeping.set(monFruit, false);
    }
};

Notre fruit tombe quand on appuie sur espace ! Sauf que l'on a qu'un seul fruit... Il faudrait qu'un autre fruit apparaisse lorsque le premier fruit est tombé. J'ai donc rajouté l'appel à la fonction ajouterFruit() à la suite de mon cas.

case "Space":
  Sleeping.set(monFruit, false);
  ajouterFruit();

Mais dans le jeu il y a un petit moment de latence entre le fruit qui est tombé et l'apparition d'un nouveau fruit et là c'est direct donc c'est moins agréable. On va alors ajouter un delais avec la fonction setTimeout avant d'ajouter un nouveau fruit.

window.onkeydown = (event) => {
    switch (event.code) {
        case "ArrowDown":
            if(interval) return ; 
            interval = setInterval(() => {
                if(monFruit.position.x - 20 > 25)
            Body.setPosition(monFruit, {
                x: monFruit.position.x - 1, 
                y: monFruit.position.y,
            });
            }, 5);
            break;
        case "ArrowRight":
            if(interval) return ; 
            interval = setInterval(() => {
                if(monFruit.position.x + 20 <400)
            Body.setPosition(monFruit, {
                x: monFruit.position.x + 1, 
                y: monFruit.position.y,
            });
            }, 5);
            break;
        case "Space":
            Sleeping.set(monFruit, false);
            setTimeout(() => {
              ajouterFruit()
            }, 1000);
    }
};

Mais ça ne suffit pas le problème persiste. Il faut donc empêcher le joueur de lacher le fruit, donc rendre la touche espace "inactive". Pour ça on va créer une variable booléenne qui permettra de savoir si lorsque j'appuie sur espace le fruit est laché ou non.

import {FRUITS} from "./fruits";

Maintenant on va créer la fonction qui permet de générer un fruit aléatoire pour succéder à celui qu'on vient de lâcher. Pour ce faire on va générer un index aléatoire qu'on appliquera au tableau pour récupérer un fruit. On a la possibilité de tomber seulement sur les 5 premiers fruits sinon ça serait trop facile. Voilà ma fonction :

function randomFruit(){
  const randomIndex = Math.floor(Math.random()*5)
  const fruit = FRUITS[randomIndex];
  return fruit;
}

Maintenant on va utiliser cette fonction pour la génération automatique du fruit d'après. On va donc modifier un peu la fonction ajouterFruit().

function ajouterFruit(){
    const randomFruit = randomFruit();

    const body = Bodies.circle(300, 150,randomFruit.radius, {
        label: randomFruit.label,
        isSleeping: true,
        render : {
            fillStyle: randomFruit.color,
        },
    });
    monFruit = body;
    World.add(world,body);
};

Sprint 2

Objectifs du second sprint

1. Faire apparaitre le fruit suivant lors de la collision de deux fruits identiques

Pour gérer la fusion des fruits, on va utiliser l'objet Events que l'on importe depuis la bibliothèque matter.js.

import {Engine, Render, Runner, Bodies, World, Body, Sleeping, Events} from 'matter-js';

On va pouvoir écouter certains type d'évenements qui peuvent arriver dans le moteur physique avec Events.on(). On va chercher à savoir si les deux éléments qui sont entrés en collision on le même label et dans ce cas supprimer ceux-là.

Events.on(engine, "collisionStart", (event) => {
  event.pairs.forEach(collision => {
    if(collision.bodyA.label == collision.bodyB.label){
      World.remove(world, [collision.bodyA, collision.bodyB]);
    }
  })
})

Lorsque deux fruits identiques se touchent ils disparaisent. Maintenant il faut faire apparaitre le fruit d'après à l'endroit de la collision. Comment savoir le fruit prochain ? Et bien le tableau FRUITS doit être ordonné par croissance des fruits pour nous faciliter la tâche. On va donc récupérer l'index du fruit qui est rentré en collision et l'incrémenter de 1 pour obtenir l'index du nouveau fruit et extraire ses propriétés du tableau.

Events.on(engine, "collisionStart", (event) => {
  event.pairs.forEach(collision => {
    if(collision.bodyA.label == collision.bodyB.label){
      World.remove(world, [collision.bodyA, collision.bodyB]);

      const index= FRUITS.findIndex((fruit) => fruit.label == collision.bodyA.label);

      const nouveauFruit = FRUITS[index + 1];
      const body = Bodies.circle(
        collision.collision.supports[0].x,
        collision.collision.supports[1].y,
        nouveauFruit.radius,
        {rendeer : {
          fillsStyle : nouveauFruit.color
        },
        label : nouveauFruit.label
        }
      )
      World.add(world,body);
    }
  });
})

Désormais quand deux fruits se rencontrent, le fruit suivant apparait ! Par contre il faut que l'on fasse attention au cas ou deux derniers fruits se rencontrent. Dans le jeu les deux pastèques disparaisent mais rien ne réapparait. On va donc rajouter cette règle dans notre fonction.

Events.on(engine, "collisionStart", (event) => {
  event.pairs.forEach(collision => {
    if(collision.bodyA.label == collision.bodyB.label){
      World.remove(world, [collision.bodyA, collision.bodyB]);

      const index= FRUITS.findIndex((fruit) => fruit.label == collision.bodyA.label);

      if(index == FRUITS.length - 1) return;

      const nouveauFruit = FRUITS[index + 1];
      const body = Bodies.circle(
        collision.collision.supports[0].x,
        collision.collision.supports[1].y,
        nouveauFruit.radius,
        {rendeer : {
          fillsStyle : nouveauFruit.color
        },
        label : nouveauFruit.label
        }
      )
      World.add(world,body);
    }
  });
})

2. Gérer la fin de la partie

Pour ça on va rajouter une ligne en haut et si un fruit dépasse la ligne notre partie prendra fin.

const lignehaut = Bodies.rectangle(310,150,620,2, {
    isStatic: true,
    isSensor: true,
    render: {fillStyle: "#9EA1D4"},
    label: "ligne",
})

World.add(world, [sol,murgauche,murdroite,lignehaut]);

La propriété isSensor permet de déterminer si l'élément n'est sensible à la collision ou non. On doit le mettre à true car sinon nos fruits vont rester bloqué par cette ligne en haut et ne tomberont pas. Pas contre on détecte quand même les collisions ce qui permet de savoir si la partie est fini ou non, on va rajouter cette règle dans le jeu sur l'écouteur Events.on().

Events.on(engine, "collisionStart", (event) => {
  event.pairs.forEach(collision => {
    if(collision.bodyA.label == collision.bodyB.label){
      World.remove(world, [collision.bodyA, collision.bodyB]);

      const index= FRUITS.findIndex((fruit) => fruit.label == collision.bodyA.label);

      if(index == FRUITS.length - 1) return;

      const nouveauFruit = FRUITS[index + 1];
      const body = Bodies.circle(
        collision.collision.supports[0].x,
        collision.collision.supports[1].y,
        nouveauFruit.radius,
        {rendeer : {
          fillsStyle : nouveauFruit.color
        },
        label : nouveauFruit.label
        }
      )
      World.add(world,body);
    }
    if((collision.bodyA.label === "ligne" || collision.bodyB.label === "ligne")&&!disableAction){
      alert("Game over");
    }
  });
})

3. Ajouter des images pour remplacer les ronds

Pour ça je me suis rendue sur Canva et j'ai cherché des images de légumes/fruits qui iront bien dans le jeu, voici ceux que j'ai sélectioné :

Le set n'est pas parfait et il y a les filligrames de Canva mais si j'ai le temps j'essaierais de trouver quelque chose de mieux à la fin. On va ajouter toutes ces images dans le dossier public et les ajouter dans le code. On fera attention de nommer les images avec le label du fruit correspondant. Par exemple garlic.png pour notre ail. Au lieu d'ajouter une couleur à nos fruits on va leur rajouter une image avec sprite.

function ajouterFruit(){
    const randomFruit = randomFruit();

    const body = Bodies.circle(300, 150,randomFruit.radius, {
        label: randomFruit.label,
        isSleeping: true,
        render : {
            fillStyle: randomFruit.color,
            sprite: {texture : '/${randomFruit.label}.png'}
        },
    });
    monFruit = body;
    World.add(world,body);
};

Il faut faire la même modification lorsque deux fruits se rencontrent et que le prochain fruit apparait.

4. Ajout d'un score pour la partie

On va rajouter un tableau de score pour notre jeu. D'abord j'ajoute une nouvelle variable score qui ajoutera les points à chaque fois que l'on ajout un fruit dans notre boite.

let score = 0;

function updateScore(points) {
    score += points;
    document.getElementById('score').innerText = `Score: ${score}`;
}

Ensuite je modifie mon tableau FRUITS en rajoutant une propriété point pour chaque fruit.

Nouveau tableau FRUITS

export const FRUITS = [
  {
    label: "radish",
    radius: 40 / 2,
    color: "#F20306",
    points: 10
  },
  {
    label: "garlic",
    radius: 50 / 2,
    color: "#FF624C",
    points: 15
  },
  {
    label: "onionw",
    radius: 72 / 2,
    color: "#A969FF",
    points: 20
  },
  {
    label: "lemon",
    radius: 85 / 2,
    color: "#FFAF02",
    points: 30
  },
  {
    label: "orange",
    radius: 106 / 2,
    color: "#FC8611",
    points: 45
  },
  {
    label: "tomato",
    radius: 140 / 2,
    color: "#F41615",
    points: 65
  },
  {
    label: "onion",
    radius: 160 / 2,
    color: "#FDF176",
    points: 90
  },
  {
    label: "paprika",
    radius: 196 / 2,
    color: "#FEB6AC",
    points: 130
  },
  {
    label: "eggplant",
    radius: 220 / 2,
    color: "#F7E608",
    points: 200
  },
  {
    label: "artichoke",
    radius: 270 / 2,
    color: "#89CE13",
    points: 300
  },
  {
    label: "pumpkin",
    radius: 300 / 2,
    color: "#26AA1E",
    points: 450
  },
];

Ensuite je vais appeler la fonction updateScore lorsque je lâche un fruit et aussi quand un nouveau fruit apparait.

case "Space":
            disableAction=true;
            const indexF = FRUITS.findIndex((fruit)=> fruit.label==fruitActuel.label);
            const monFruit = FRUITS[indexF] ; 
            Sleeping.set(fruitActuel, false);
            setTimeout(() => {
                ajouterFruit();
                disableAction = false;
            },1000);
            updateScore(monFruit.points);
Events.on(engine,"collisionStart",(event)=>{
    event.pairs.forEach(collision => {
        if(collision.bodyA.label == collision.bodyB.label){
            World.remove(world, [collision.bodyA, collision.bodyB]);

            const index = FRUITS.findIndex((fruit )=> fruit.label==collision.bodyA.label);

            if(index == FRUITS.length - 1) return;
            
            const newfruit = FRUITS[index + 1];
            const body = Bodies.circle(
                collision.collision.supports[0].x,
                collision.collision.supports[0].y,
                newfruit.radius,
                {render: {
                    fillStyle: newfruit.color,
                    sprite: { texture: `/${newfruit.label}.png` },
                },
            label : newfruit.label,
        });
        updateScore(newfruit.points);
        World.add(world, body);

        }
        if((collision.bodyA.label === "ligne" || collision.bodyB.label === "ligne")&&!disableAction){
            alert("Game over");
          } 
    })
});

J'améliore un peu l'hmtl et j'obtiens ça :

Ensuite j'ai changé les couleurs de la boite et du fond et j'ai rajouté un bouton pour lancer une nouvelle partie. Pour cela j'ai supprimé tous les corps autre que les constituants de la boite et mis le score à zero lorsque le joueur clique sur le bouton.

document.getElementById('newGameButton').addEventListener('click', () => {
    score = 0;
    updateScore(score);
    world.bodies.filter(body => body.label !== "sol" && body.label !== "ligne" && body.label !== "murgauche"&& body.label !== "murdroite").forEach(body => {
        World.remove(world, body);
    });
    ajouterFruit();
 } );

Conclusion et améliorations possibles

Le jeu final est très proche du jeu auquel on joue sur téléphone, néanmoins de nombreux choses peuvent être améliorés.

Voici d'autres améliorations possibles :

Cliquez ici pour télécharger le fichier zip.

Horodateur

Date Heures passées Indications
Lundi 22/01 1H Découvrir rapidement Matter.js
Mardi 22/01 2H Créer l'environnement de code avec les différents modules utiles
Mercredi 23/01 1H Créer la boite du jeu
Mercredi 23/01 15min Créer un rond qui tombe
Jeudi 24/01 1H30 Faire en sorte que le rond ne tombe pas tout de suite
Samedi 27/01 2H Pouvoir faire bouger le rond avec les flèches
Samedi 27/01 1H Pouvoir lâcher le rond avec la barre espace
Mardi 30/01 2H Générer aléatoirement plusieurs tailles de rond
-------- -------- --------
Jeudi 01/02 1H30 Détecter une collision entre deux élements
Jeudi 01/02 30min Supprimer les deux élements si ce sont les mêmes fruits
Samedi 03/02 1H30 Faire apparaitre le fruit suivant à l'endroit de la collision
Samedi 03/02 15min Créer une ligne de délimition en haut
Samedi 03/02 1H Arrêter la partie quand un fruit dépasse la ligne
Dimanche 04/02 1H Trouver des images de fruits/légumes
Dimanche 04/02 30min Redimensionner et enlever le background des images
Dimanche 04/02 15min Ajouter les images dans le projet
Dimanche 04/02 1H30 Ajouter les images dans le code
Vendredi 16/02 1H Ajout d'un score et modification de couleurs