Aller au contenu

CCINP  2024

Note

  • Ce corrigé peut contenir des erreurs ! Merci de me les signaler si vous en trouvez
  • Un code complet permettant de jouer à l'awale en joueur contre joueur ou contre une IA (algorithme min max) et crée au fil des questions est donné en fin de corrigé

Enoncé

Enoncé

Partie I - Présentation et règles

Question 1

Alice peut choisir de jouer n'importe quelle case non vide de son camp, donc ici les cases 2, 4 et 5

Question 2

La situation initiale est :

1
0
1
0
1
2
0
0
2
0
16
5

Voici les différentes configurations obtenues selon le coup joué :

  • Si Alice joue la case 2, son gain est nul
1
0
1
0
1
2
0
0
0
1
17
5
  • Si Alice joue la case 4 elle récolte les cases 7 (3 graines), 8 (2 graines) et 9 (3 graines) pour un gain de 8:
2
1
0
0
0
4
1
1
3
1
0
7
  • Si Alice joue la case 5, son gain est nul
1
1
2
1
2
3
0
0
2
0
16
0

Question 3

Dans les deux situations, Alice ne peut jouer que la case 5.

  • Dans la première situation, on obtient :
0
2
2
2
2
2
0
0
0
0
0
0

Mais la récolte d'Alice affame alors son adversaire et donc le coup n'est pas valide !

  • Dans la seconde situation, on obtient :
1
0
0
0
0
0
0
0
0
0
0
0

Car Alice peut alors récolter les cases 6 à 10 (qui contiennent chacune 2 graines) pour un gain de 10.

Partie II - Programmation de la structure du jeu

Question 4

    def tour_joueur1(jeu):
        '''Renvoie True si c'est le tour du joueur 1, False sinon.'''
        # jeu['n'] est nombre de tour déjà joués c'est le tour du joueur 1 lorsque jeu['n'] est pair
        return jeu['n'] % 2 == 0

Question 5

    def tourner_plateau(jeu):
        '''Ne renvoie rien et modifie le plateau pour inverses les cases des joueurs 1 et 2'''
        jeu['plateau'][0:6], jeu['plateau'][6:12] = jeu['plateau'][6:12], jeu['plateau'][0:6]

Question 6

Au plus, une case contient la totalité des graines donc 48. Donc 6 bits sont nécessaires pour stocker le nombre de graines de chaque case comme \(2^6 = 64\), on peut représenter sur 6 bits les entiers de \(0\) à \(65\).

Question 7

    def copie(jeu):
        '''Renvoie une copie du dictionnaire jeu'''
        copie_jeu = {}
        copie_jeu['joueur1'] = jeu['joueur1']
        copie_jeu['joueur2'] = jeu['joueur2']
        copie_jeu['score'] = jeu['score'].copy()
        copie_jeu['n'] = jeu['n']
        copie_jeu['plateau'] = jeu['plateau'].copy()
        return copie_jeu

Question 8

    def deplacer_graines(plateau, case):
        a_semer = plateau[case]
        plateau[case] = 0
        # La prochaine position où on va semer
        pos = (case + 1) % 12
        while a_semer != 0:
            plateau[pos] += 1
            a_semer -= 1
            pos = (pos + 1) % 12
            # On ne doit pas semer dans la case initiale
            if (pos == case):
                pos = (pos + 1) % 12
        return (pos-1) % 12

Note

On pouvait aussi utiliser la fonction d'initialisation awale_jcj donnée dans l'énoncé pour initialiser la copie et rensuite recopier ensuite les éléments différents.

Question 9

    def case_ramassable(plateau, case):
        '''Renvoie True si la case est ramassable, False sinon'''
        return (6 <= case <= 11) and (2 <= plateau[case] <= 3)

Question 10

    def ramasser_graines(plateau, case):
        '''Ramasse les graines de la case et renvoie le nombre de graines ramassées'''
        # le cas de base
        if not case_ramassable(plateau, case):
            return 0
        # l'appel récursif
        recolte = plateau[case]
        plateau[case] = 0
        return recolte + ramasser_graines(plateau, case - 1)

Question 11

    def test_famine(plateau, case):
        '''Renvoie True lorsque la récolte ne conduit pas à la famine de l'adversaire'''
        copie_plateau = plateau.copy()
        derniere_case = deplacer_graines(copie_plateau, case)
        recolte = ramasser_graines(copie_plateau, derniere_case)
        # On parcourt les cases de l'adversaire si l'une contient des graines, il n'y a pas famine
        for i in range(6, 12):
            if copie_plateau[i] != 0:
                return True
        return False

Question 12

On a surligné la ligne à compléter (on vérifie simplement que les 3 conditions de l'énoncé sont valides)

    def test_case(plateau, case):
        '''Vérfie si la case choisie par le joueur est acceptable 
        renvoie True si la case est acceptable, False sinon'''
        condition3 = test_famine(plateau, case)
        # case acceptable
        test = (0 <= case <= 5) and (plateau[case] != 0) and condition3
        return test

Question 13

    def cases_possibles(jeu):
        '''Renvoie la liste des cases jouables'''
        plateau = jeu['plateau']
        return [i for i in range(6) if test_case(plateau, i)]

Question 14

    def tour_suivant(jeu):
        '''Renvoie True si le jeu peut continuer'''
        # On vérifie les conditions de fin de partie (voir page 9 du sujet)
        # 1. Un joueur a récolté au moins 25 graines
        if jeu['score'][0] >= 25 or jeu['score'][1] >= 25:
            return False
        # 2. Le nombre de tours joués >= 100
        if jeu['n']>=100:
            return False
        # 3. Il reste moins de 3 graines sur le plateau
        reste = 0
        for graines in jeu['plateau']:
            reste += graines
        if reste <= 3:
            return False
        # 4. Le joueur actif n'a pas de coup valide
        if len(cases_possibles(jeu)) == 0:
            return False
        return True

Question 15

    def tour_jeu(jeu, case):
        plateau = jeu['plateau']
        if test_case(plateau, case):  # La case jouée est acceptable
            # Semer et récupérer la derniere case atteinte
            derniere_case = deplacer_graines(plateau, case)
            # Récolter depuis la derniere case
            graines_gagnees = ramasser_graines(plateau, derniere_case)
            if tour_joueur1(jeu):
                jeu['score'][0] += graines_gagnees
            else:
                jeu['score'][1] += graines_gagnees
            jeu['n'] += 1
            tourner_plateau(jeu)
            return tour_suivant(jeu)
        else:
            print("La case choisie n'est pas valide !")
            return True

Question 16

    def gagnant(jeu):
        # Suivant le joueur actif on regarde quelle partie du plateau ramsser
        if tour_joueur1(jeu):
            for i in range(0, 6):
                jeu['score'][0] += jeu['plateau'][i]
            for i in range(6, 12):
                jeu['score'][1] += jeu['plateau'][i]
        else:
            for i in range(6, 12):
                jeu['score'][0] += jeu['plateau'][i]
            for i in range(0, 6):
                jeu['score'][1] += jeu['plateau'][i]
        if jeu['score'][0] > jeu['score'][1]:
            return jeu['joueur1']
        elif jeu['score'][0] < jeu['score'][1]:
            return jeu['joueur2']
        else:
            return "égalité"

Partie III - Programmation de l'Intelligence Artificielle (IA)

Question 17

    def gain(jeu, case):
        '''Renvoie le nombre de graines gagnées et un nouveau dictionnaire'''
        njeu = copie(jeu)
        plateau = njeu["plateau"]
        # Semer et récupérer la derniere case atteinte
        derniere_case = deplacer_graines(plateau, case)
        # Récolter depuis la derniere case
        graines_gagnees = ramasser_graines(plateau, derniere_case)
        if tour_joueur1(jeu):
            jeu['score'][0] += graines_gagnees
        else:
            jeu['score'][1] += graines_gagnees
        njeu['n'] += 1
        tourner_plateau(njeu)
        return graines_gagnees, njeu

Question 18

arbre complété

Alice jouera la case 5 (branche centrale), en effet la valeur de jeu remontée pour cette branche est 2 (elle gagne 2 graines et Bob ne peut obtenir aucun gain au coup suivant). La branche gauche remonte une valeur de jeu nulle (elle gagne 4 graines, mais un des coups suivants de Bob lui permet aussi de gagner 4 graines) et il en est de même pour la branche droite.

Question 19

Les lignes qui devaient être complétées sont surlignées

    def Negawale(jeu, profondeur_max, profondeur):
        #Ce sont les conditions décrites à la page 12 du sujet : noeud feuille, profondeur maximale atteinte ou autre
        # Condition1 : noeud feuille (le jeu se termine)
        if (not tour_suivant(jeu)):
            if (tour_joueur1(jeu) and gagnant(jeu)==jeu["joueur1"]) or (not tour_joueur1(jeu) and gagnant(jeu)==jeu["joueur2"]):
                return 500
            elif (tour_joueur1(jeu) and gagnant(jeu)==jeu["joueur2"]) or (not tour_joueur1(jeu) and gagnant(jeu)==jeu["joueur1"]):
                return -500
            else:
                return 0
        # Condition 2 : profondeur maximale atteinte
        elif profondeur==profondeur_max:
            return 0
        else:
            choix_cases = cases_possibles(jeu)
            vals_jeu = []
            for case in choix_cases:
                g, nouveau_jeu = gain(jeu, case)
                p = Negawale(nouveau_jeu,profondeur_max, profondeur+1)
                vals_jeu.append([case, g-p])
            return max_vals(vals_jeu, profondeur)

Question 20

    def max_vals(vals_jeu, profondeur):
        # On recherche l'indice du maximum
        ind_max = 0
        for i in range(1, len(vals_jeu)):
            # vals_jeu contient des couples (case, valeur de jeu) 
            if vals_jeu[i][1] > vals_jeu[ind_max][1]:
                ind_max  = i
        # Les deux cas à distinguer (le noeud de départ correspond à une profondeur nulle)
        if profondeur==0:
            return vals_jeu[ind_max][0]
        else:
            return vals_jeu[ind_max][1]

Question 21

La ligne modifiée par rapport à awale_jcj a été surlignée.

    def awale_ia(nom_joueur1, nom_joueur2):
        jeu = initialisation(nom_joueur1, nom_joueur2)
        jeu_continue = True
        while jeu_continue:
            affiche(jeu)
            case_choisie = Negawale(jeu, 6, 0)
            jeu_continue = tour_jeu(jeu, case_choisie)
            _ = input("")
        return gagnant(jeu)

Question 22

SELECT id_Joueur
FROM Joueur
WHERE niveau>1900

Question 23

La requête suivante donne le nombre de parties victorieuse pour le joueur 1 lorsque la case "0" (lettre 'a') a été jouée au premier coup, on l'utilise dans une sous requête dans la réponse

SELECT COUNT(*)
FROM Partie
WHERE jeu LIKE "a%" AND resultat=1;

SELECT 100.0 * (SELECT COUNT(*)
        FROM Partie
        WHERE jeu LIKE "a%" AND resultat=1) / COUNT(*)
FROM Partie WHERE jeu LIKE "a%";

Question 24

SELECT nom, prenom
FROM Joueur
ORDER BY niveau DESC
LIMIT 3 ;

Question 25

Totalité du programme Awalé

Fichier Python complet du jeu