Comment réaliser un morpion en kotlin ?

Voici mon nouvel  article ! Dans cet article je vais vous expliquez comment vous allez pouvoir réaliser un morpion en kotlin. Je vous invite à lire mon précèdent article “Comment réaliser une page de renseignement en kotlin ?” et l’article “Comment initialiser et manipuler les tableaux en kotlin ?”. Ils abordent des notions intéressantes que j’utilise notamment dans cette application.

Pour comprendre la suite de l’article, il est nécessaire d’avoir assimilé les notions de tableau, de classe et d’objet. Si vous ne les connaissez pas, je vous invite également à lire mes précédents articles.

Le principe 

Le morpion est un jeu de plateau qui se joue à deux joueurs. L’objectif pour chaque joueur est d’aligner son symbole dans une grille de 3×3  verticalement, horizontalement où en diagonale pour remporter la partie. Chaque case ne peut pas être remplie plus d’une fois par partie. Pendant la partie si toutes les cases sont remplies alors qu’aucun des joueurs n’a aligné trois fois son symbole, la partie se termine sans vainqueur.

Les fonctionnalités

Pour rendre le jeu plus immersif, j’ai mis en place une page de renseignement où  j’amène les joueurs à rentrer leurs pseudo. Les pseudo vont me permettre d’ indiquer à quel  joueur c’est le tour de jouer et d’afficher le vainqueur de la partie.

Pour éviter des erreurs d’affichage par la suite, j’utilise les mêmes propriétés de la page de renseignement que je vous avais présenté précédemment. C’est-à-dire les pseudo des joueurs ne peuvent pas dépasser plus de 9 caractères.

j’ai également implémenté une fonctionnalité pour déterminer de façon aléatoire le joueur qui débutera la partie. C’est une fonctionnalité que j’avais déjà utilisée dans mon application “juste Prix” .

Le visuel de l’application

l’application va posséder deux activités, une pour l’enregistrement des joueurs et une autre pour la partie de jeu :

Demo_Morpion_01_thumb1   Demo_Morpion_02_thumb1

La première activity

Je vous invite à suivre mon article “Comment réaliser une page de renseignement en kotlin ?”, si vous souhaitez mettre en place la même activité.

La deuxième activity

Cette activity se décompose en deux couches principales (2 LinearLayout vertical), une couche pour la grille et le texte qui annonce le tour du joueur et une couche pour le bouton “Relancer”.

La grille

code xml
 <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:weightSum="90">
        <Button

                android:layout_width="@dimen/size_case"
                android:layout_height="@dimen/size_case"
                android:layout_weight="30"
                android:id="@+id/button"/>
        <LinearLayout android:layout_width="10dp"
              android:layout_height="match_parent"
              android:background="@color/Noir"
        />
        <Button

                android:layout_width="@dimen/size_case"
                android:layout_height="@dimen/size_case"
                android:layout_weight="30"
                android:id="@+id/button2"/>
        <LinearLayout android:layout_width="10dp"
              android:layout_height="match_parent"
              android:background="@color/Noir"
        />
        <Button

                android:layout_width="@dimen/size_case"
                android:layout_height="@dimen/size_case"
                android:layout_weight="30"
                android:id="@+id/button3"/>
    </LinearLayout>
    <LinearLayout android:layout_width="match_parent" android:background="@color/Noir"
          android:layout_height="10dp"></LinearLayout>
    <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:weightSum="90"
    >
        <Button
                android:layout_width="@dimen/size_case"
                android:layout_height="@dimen/size_case"
                android:layout_weight="30"
                android:id="@+id/button4"/>
        <LinearLayout android:layout_width="10dp"
              android:layout_height="match_parent"
              android:background="@color/Noir"
        />
        <Button

                android:layout_width="@dimen/size_case"
                android:layout_height="@dimen/size_case"
                android:layout_weight="30"
                android:id="@+id/button5"/>
        <LinearLayout android:layout_width="10dp"
              android:layout_height="match_parent"
              android:background="@color/Noir"
        />
        <Button

                android:layout_width="@dimen/size_case"
                android:layout_height="@dimen/size_case"
                android:layout_weight="30"
                android:id="@+id/button6"/>

    </LinearLayout>
    <LinearLayout android:layout_width="match_parent" android:background="@color/Noir"
          android:layout_height="10dp"></LinearLayout>
    <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:weightSum="90"
    >
        <Button

                android:layout_width="@dimen/size_case"
                android:layout_height="@dimen/size_case"
                android:layout_weight="30"
                android:id="@+id/button7"/>
        <LinearLayout android:layout_width="10dp"
              android:layout_height="match_parent"
              android:background="@color/Noir"
        />
        <Button

                android:layout_width="@dimen/size_case"
                android:layout_height="@dimen/size_case"
                android:layout_weight="30"
                android:id="@+id/button8"/>
        <LinearLayout android:layout_width="10dp"
              android:layout_height="match_parent"
              android:background="@color/Noir"
        />
        <Button

                android:layout_width="@dimen/size_case"
                android:layout_height="@dimen/size_case"
                android:layout_weight="30"
                android:id="@+id/button9"/>

    </LinearLayout>
</LinearLayout>

Elle se décompose en 3 lignes de bouton (3 LinearLayout Horizontale) et deux lignes de séparation (2 LinearLayout Horizontale).

La ligne de boutons

code xml
<LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:weightSum="90">
    <Button

            android:layout_width="@dimen/size_case"
            android:layout_height="@dimen/size_case"
            android:layout_weight="30"
            android:id="@+id/button"/>
    <LinearLayout android:layout_width="10dp"
          android:layout_height="match_parent"
          android:background="@color/Noir"
    />
    <Button

            android:layout_width="@dimen/size_case"
            android:layout_height="@dimen/size_case"
            android:layout_weight="30"
            android:id="@+id/button2"/>
    <LinearLayout android:layout_width="10dp"
          android:layout_height="match_parent"
          android:background="@color/Noir"
    />
    <Button

            android:layout_width="@dimen/size_case"
            android:layout_height="@dimen/size_case"
            android:layout_weight="30"
            android:id="@+id/button3"/>
</LinearLayout>

Elle se décompose en 3  boutons qui se suivent horizontalement avec ligne de séparation (2 LinearLayout Horizontale) entre chaque bouton pour donner l’aspect de grille.

La ligne de séparation

code xml
<LinearLayout android:layout_width="match_parent" android:background="@color/Noir"
      android:layout_height="10dp"></LinearLayout>

La ligne de séparation est un LinearLayout avec une épaisseur de 10 dp (layout_height  quand la ligne est horizontale et layout_width lorsqu’elle est verticale) et une couleur de fond noir (background).

La deuxième couche principale qui contient le bouton “Relancez”  qui est centré et désactivé par défaut.

Le lancement de la partie (deuxième activité)

La seconde activité a le rôle de lancer la partie. Dans un premier temps on initialise (avec la méthode initialiserLaPartie) puis on lance la partie ( avec la méthode tourDeJeu).

La méthode initialiserLaPartie :
code
fun initialiserLaPartie(plateau:ArrayList<Button>)
{
    buttonRelancer.isEnabled = false
    User = initialisationOrdreJoueur (intent.getStringExtra("Joueur1"),intent.getStringExtra("Joueur2"))
    AvertisseurJoueurEnCours.text = "c'est au tour de "+User[NumeroDuJoueurEnCour].nom
    tableau = arrayOf(2,2,2,2,2,2,2,2,2)

    loop@ plateau.forEach{
        it.setBackgroundDrawable(null)
        it.isClickable = true
    }
    tourDeJeu(plateau)

}

Cette méthode est prise en compte au premier lancement de l’activité car elle est présente dans la méthode OnCreate et lors du Click sur le bouton “Relancer”. Elle effectue les actions suivantes :

  • Afin de bloquer toute relance en cours de partie, le bouton “Relancer” est initialisé à false (inutilisable).
  • La méthode initialisationOrdreJoueur est appelée pour initialiser l’ordre des joueurs. Cette méthode va demander en paramètre les deux pseudo des joueurs. Les pseudo des joueurs qu’on récupère avec les extra qu’on a définis avec un tag dans l’activité précédente (intent.getStringExtra).
  • Le textView au-dessus de la grille est initialisé avec le nom du joueur déterminé avec la méthode précédente.
  • La variable globale “tableau”  est initialisé avec des nombres différents de 0 et 1. Les chiffres 0 et 1 correspondent aux valeurs des joueurs lorsque qu’ils remplissent le tableau (0 –> joueur 1, 1 –> joueur 2).
  • Le plateau est réinitialisé (boucle forEach pour parcourir chaque case) pour effacer les symboles et rendre cliquables les cases.
  • Pour finir la méthode tourDeJeu est appelée pour lancer ou relancer la partie.
La méthode initialisationOrdreJoueur :

code
fun initialisationOrdreJoueur(pseudo1:String,pseudo2:String): ArrayList<Joueur> {
    //val rand = Random()
    val nbAleatoire = Random.nextInt(0,2)



    if (nbAleatoire === 1)
    {
        val joueur1 = Joueur(pseudo2,getDrawable(R.drawable.o))
        val joueur2 = Joueur(pseudo1,getDrawable(R.drawable.x))
        val Joueurs = arrayListOf<Joueur>(joueur1,joueur2)
        return Joueurs
    }else
    {
        val joueur1 = Joueur(pseudo1,getDrawable(R.drawable.o))
        val joueur2 = Joueur(pseudo2,getDrawable(R.drawable.x))
        val Joueurs = arrayListOf<Joueur>(joueur1,joueur2)
        return Joueurs
    }

}

Un joueur possède un pseudo et un symbole, il est donc plus simple de le représenter par une classe. Cette méthode va venir créer les joueurs et les stocker dans un tableau d’objet.

L’ordre des joueurs va être déterminé de façon aléatoire. Le premier pseudo choisit au hasard (pseudo 1 ou 2 définit en paramètre de la méthode) sera le premier joueurs à commencer la partie. Chaque joueur est initialisé avec son pseudo et son symbole. Le premier joueur est initialisé avec le symbole du rond et le second joueurs avec le symbole de la croix. Une fois les joueurs bien initialisés, je les stocke dans le tableau global (User).

drawable_o_x

En réalité les symboles sont des images, que j’ai ajoutées à mon application dans le dossier “drawable”. Un simple copié-collé permet de les ajouter.

Vous pouvez récupérer les images ici : ( clique droit, puis “télécharger l’image”).

o      x

La méthode tourDeJeu :
code
fun tourDeJeu(plateau:ArrayList<Button>)
{
  var  nbCaseRemplis : Int = 0

   loop@ plateau.forEach{


        it.setOnClickListener {
            it.setBackgroundDrawable(User[NumeroDuJoueurEnCour].symbole)
            it.isClickable = false
            remplirPlateau(it as Button)
            nbCaseRemplis++

           if (!verification3MemeSymbole()){
                if (NumeroDuJoueurEnCour === 0)
                {
                    NumeroDuJoueurEnCour=1

                }
                else
                {
                    NumeroDuJoueurEnCour=0
                }
                AvertisseurJoueurEnCours.text = "c'est au tour de "+User[NumeroDuJoueurEnCour].nom
           }
            else
           {
               AvertisseurJoueurEnCours.text = User[NumeroDuJoueurEnCour].nom+" a gagné"
               buttonRelancer.isEnabled = true
               return@setOnClickListener
           }
            if ((nbCaseRemplis === 9)){
                AvertisseurJoueurEnCours.text = " égalité !"
                buttonRelancer.isEnabled = true
                return@setOnClickListener
        }
    }
   }
}

Cette méthode est lancée après chaque initialisation de la partie (méthode initialiserLaPartie ). Elle demande en paramètre un plateau (un arraylist de bouton) qui correspond à notre grille.

Un arraylist : C’est un conteneur qui peut contenir un ensemble variable de typologie différente.

L’ensemble des instructions de la méthode va interagir sur ce plateau.

La méthode effectue les actions suivantes :

  • Elle vient créer un compteur d’itération “nbCaseRemplis”. Ce compteur va permettre à la méthode de connaitre l’instant d’égalité où toutes les cases sont remplies mais aucun des joueurs n’a aligné trois fois son symbole.
  • Elle initialise une boucle  “forEach” sur l’ensemble des boutons afin de récupérer chaque click sur un bouton de l’utilisateur.
  • Elle affiche le pseudo du joueur pour lequel c’est le tour de jouer avec la variable globale “NumeroDuJoueurEnCour”. La variable NumeroDuJoueurEnCour est une variable globale qui nous permet de savoir à quel joueur c’est le tour de jouer. À chaque début de partie, elle est initialisée par le numéro du premier joueur (0 –> joueur n°1).

À chaque clic sur une des cases :

  • On remplit la case par le symbole du joueur où c’est le tour de jeu (User[NumeroDuJoueurEnCour].symbole).
  • On rend la case inutilisable, afin de bloquer tout nouveau remplissage.
  • On appelle la méthode “remplirPlateau” avec la case en paramètre.

Puis on vérifie :

  • Si le joueur vient d’aligner 3 même symbole avec la méthode
  • (verification3MemeSymbole ), dans ce cas on affiche à l’écran qu’il est le gagnant de la partie et on termine la partie en rendant réutilisable le bouton “Relancer”.
  • Si le joueur en cours était le joueurs n°1,  dans ce cas on  change la variable par la valeur 1 (joueur n°2).  Sinon on change la variable par la valeur 2 (joueur n°1).
  • Si toutes les cases sont remplies (on vérifie notre compteur “nbCaseRemplis”), dans ce cas on indique alors à l’écran une égalité et on termine la partie en rendant réutilisable le bouton “Relancer”  .

Si la partie n’est pas terminée, alors on passe à un nouveau tour.

La méthode remplirPlateau :
code
fun remplirPlateau(idButton:Button){

    when (idButton.id)
    {
        R.id.button ->  {tableau[0] = NumeroDuJoueurEnCour}
        R.id.button2 -> {tableau[1] = NumeroDuJoueurEnCour}
        R.id.button3 -> {tableau[2] = NumeroDuJoueurEnCour}
        R.id.button4 -> {tableau[3] = NumeroDuJoueurEnCour}
        R.id.button5 -> {tableau[4] = NumeroDuJoueurEnCour}
        R.id.button6 -> {tableau[5] = NumeroDuJoueurEnCour}
        R.id.button7 -> {tableau[6] = NumeroDuJoueurEnCour}
        R.id.button8 -> {tableau[7] = NumeroDuJoueurEnCour}
        R.id.button9 -> {tableau[8] = NumeroDuJoueurEnCour}
    }
}

Cette méthode va nous servir de support pour mémoriser les cases remplies par les joueurs. En fonction de l’identifiant du bouton qu’on transmet en paramètre et le NumeroDuJoueurEnCour, les cases seront remplies en arrière-plan dans un tableau global (“tableau”). À chaque bouton (identifiant) correspond une case. Lorsque l’utilisateur remplit une case, le numéro du joueur qui à remplis cette case est inscrit dans le tableau. L’objectif de cette méthode est de par la suite de permettre la vérification de l’alignement de chaque symbole par le joueur (méthode verification3MemeSymbole).

La méthode verification3MemeSymbole :
code
fun verification3MemeSymbole () :Boolean
{
    if(verification3MemeSymboleDiagonal()){return true }
    if (verification3MemeSymboleVertical()){return true }
    if (verification3MemeSymboleHorizontale()){return true }
    return false
}

fun verification3MemeSymboleHorizontale () :Boolean
{
    if (tableau[0] === NumeroDuJoueurEnCour && tableau[3] === NumeroDuJoueurEnCour && tableau[6] === NumeroDuJoueurEnCour )
    { return true }
    if (tableau[1] === NumeroDuJoueurEnCour && tableau[4] === NumeroDuJoueurEnCour && tableau[7] === NumeroDuJoueurEnCour )
    { return true }
    if (tableau[2] === NumeroDuJoueurEnCour && tableau[5] === NumeroDuJoueurEnCour && tableau[8] === NumeroDuJoueurEnCour )
    { return true }

    return false

}
fun verification3MemeSymboleVertical () :Boolean
{
    if (tableau[0] === NumeroDuJoueurEnCour && tableau[1] === NumeroDuJoueurEnCour && tableau[2] === NumeroDuJoueurEnCour )
    { return true }
    if (tableau[3] === NumeroDuJoueurEnCour && tableau[4] === NumeroDuJoueurEnCour && tableau[5] === NumeroDuJoueurEnCour )
    { return true }
    if (tableau[6] === NumeroDuJoueurEnCour && tableau[7] === NumeroDuJoueurEnCour && tableau[8] === NumeroDuJoueurEnCour )
    { return true }

    return false
}
fun verification3MemeSymboleDiagonal () :Boolean
{
    if (tableau[0] === NumeroDuJoueurEnCour && tableau[4] === NumeroDuJoueurEnCour && tableau[8] === NumeroDuJoueurEnCour )
    { return true }
    if (tableau[2] === NumeroDuJoueurEnCour && tableau[4] === NumeroDuJoueurEnCour && tableau[6] === NumeroDuJoueurEnCour )
    { return true }

    return false
}

Cette méthode regroupe 3 méthodes qui va nous permettre de vérifier, si un joueurs a rempli successivement de façon verticalement, horizontalement où en diagonale les cases du “tableau”.

Les 3 méthodes qu’elle regroupe sont :

  • verification3MemeSymboleHorizontale : Cette méthode analyse le tableau de façon horizontale.
  • verification3MemeSymboleVertical : Cette méthode analyse le tableau de façon vertical.
  • verification3MemeSymboleDiagonal : Cette méthode analyse le tableau en diagonale.

Si l’une de ces trois méthodes nous retourne vrai alors cela signifie qu’un des joueurs à aligner 3 fois le même symbole et donc la méthode nous retourne vrai (True).

La méthode OnCreate :
code
class Joueur  (var nom:String, var symbole:Drawable)

lateinit var Cases : Array<Button>
lateinit var case1 : Button
lateinit var case2 : Button
lateinit var case3 : Button
lateinit var case4 : Button
lateinit var case5 : Button
lateinit var case6 : Button
lateinit var case7 : Button
lateinit var case8 : Button
lateinit var case9 : Button

lateinit var AvertisseurJoueurEnCours : TextView
lateinit var User:ArrayList<Joueur>
lateinit var buttonRelancer: Button
var NumeroDuJoueurEnCour = 0

lateinit var tableau: Array<Int>

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_morpion)

    //initialisation de tous les boutons
     case1 = findViewById(R.id.button)
     case2 = findViewById(R.id.button2)
     case3 = findViewById(R.id.button3)
     case4 = findViewById(R.id.button4)
     case5 = findViewById(R.id.button5)
     case6 = findViewById(R.id.button6)
     case7 = findViewById(R.id.button7)
     case8 = findViewById(R.id.button8)
     case9 = findViewById(R.id.button9)

    buttonRelancer = findViewById(R.id.buttonRelancer)
    AvertisseurJoueurEnCours = findViewById(R.id.auTourDe)
    var plateau = arrayListOf(case1,case2,case3,case4,case5,case6,case7,case8,case9)

    initialiserLaPartie(plateau)

    buttonRelancer.setOnClickListener{
        buttonRelancer.isEnabled = false
        initialiserLaPartie(plateau)
    }

}

Dans cette méthode, on vient initialiser nos composants, on vient initialiser la partie et on ajoute les deux événements lors du clic sur le bouton “Relancer”.

  • La première action consiste à rendre inutilisable le bouton “Relancer” pour qu’on ne puisse pas relancer la partie en cours de partie.
  • La seconde action consiste à lancer la méthode initialiserLaPartie. elle va initialiser la partie et lancer la partie (méthode “tourDeJeu”) .

Merci d’avoir lu ! Maintenant, si vous avez aimé le contenu de l’article, si  vous connaissez une personne à qui cet article peut intéresser, n’hésitez pas à le liker et à le partager sur les réseaux sociaux. Si  vous souhaitez me faire part de vos remarques, de vos problèmes, de vos suggestions de prochain article ou tout simplement de votre soutien n’hésitez pas à m’en faire part dans les commentaires, je vous en remercie.

Credit : photo de geralt, photo de Andi_Graf, photo de DIYandStyle, photo de qimono, photo de natureaddict, photo de FirmBee.

Partager l'article :
  •  
  •  
  •  
  •  
  • 6
    Partages

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *