Puissance 4

1. Fonction utilitaires

1.1. Jouer un coup

def jouer_coup(state, col):
    assert 0 <= col < 7 and state["libres"][col] > 0
    state["grille"][state["libres"][col]-1][col] = state["joueur"]
    state["joueur"] = 3 - state["joueur"]
    state["vides"] -= 1
    state["libres"][col] -= 1

1.2. Annuler coup

def annuler_coup(state, col):
    state["grille"][state["libres"][col]][col] = 0
    state["joueur"] = 3 - state["joueur"]
    state["vides"] += 1
    state["libres"][col] += 1

1.3. Coup Gagnant

def coup_gagnant(state, col):
    g = state["grille"]
    jo = 3-state["joueur"]
    i, j = state["libres"][col], col
    # On parcourt les 7 cases encadrant i,j dans chaque direction
    # Le choix de la direction se fait par le choix du vecteur directeur 
    for i_dir, j_dir in [(0,1), (1,0), (1,1), (1,-1)]:
        nb_cons = 0 # Nb de jeton consécutifs
        for d in range(-3,4): 
            if (0 <= i+d*i_dir < 6
                and 0 <= j+d*j_dir < 7
                and g[i+d*i_dir][j+d*j_dir] == jo):
                nb_cons += 1
                # Gagnant
                if nb_cons == 4:
                    return True
            else:
                nb_cons = 0
    return False

2. Stratégies

2.1. Stratégie aléatoire

import random as rd

def strategie_alea(state):
    assert state["vides"] > 0
    li = state["libres"]
    i_libres = []
    for i in range(len(li)):
        if li[i] > 0:
            i_libres.append(i)
    return rd.choice(i_libres)

2.2. Partie

def partie(stratR, stratJ):
    state = {"grille": [ [0]*7 for i in range(6) ],
             "joueur": 1,
             "libres": [6]*7,
             "vides": 6*7
            }
    strats = (stratR, stratJ)
    while state["vides"] > 0:
        st = strats[state["joueur"]-1]
        coup = st(state)
        jouer_coup(state, coup)
        if coup_gagnant(state, coup):
            return 3-state["joueur"]
    return 0
score = [0,0,0]
for i in range(10000):
    score[partie(strategie_alea, strategie_alea)] += 1
rouge = score[1] / 100

2.3. Heuristique

def h(state):
    score = 0
    gr = state["grille"]
    for i in range(6):
        for j in range(7):
            # la formule envoie (0,1,2) sur (0,1,-1)
            score += points[i][j] * (gr[i][j] % 2 - gr[i][j] // 2)
    return score

2.4. MinMax

def imin(l):
    i_min = 0
    m = l[0] 
    for i in range(1, len(l)):
        if m > l[i]:
            m = l[i]
            i_min = i
    return i_min, m
def imax(l):
    i_max = 0
    m = l[0] 
    for i in range(1, len(l)):
        if m < l[i]:
            m = l[i]
            i_max = i
    return i_max, m
def minmax(h, p, state):
    if p == 0:
        return None, h(state)
    if state["vides"] == 0:
        return None, 0
    li = state["libres"]
    i_libres = []
    for i in range(len(li)):
        if li[i] > 0:
            i_libres.append(i)
    if state["joueur"] == 1:
        scores = []
        for coup in i_libres:
            jouer_coup(state, coup)
            if coup_gagnant(state, coup):
                scores.append(float("inf"))
            else:
                scores.append(minmax(h, p-1, state)[1])
            annuler_coup(state, coup)
        i_coup, s = imax(scores)
        return i_libres[i_coup], s
    else:
        scores = []
        for coup in i_libres:
            jouer_coup(state, coup)
            if coup_gagnant(state, coup):
                scores.append(-float("inf"))
            else:
                scores.append(minmax(h, p-1, state)[1])
            annuler_coup(state, coup)
        i_coup, s = imin(scores)
        return i_libres[i_coup], s
     
def strategie_minmax(h, p, state):
    coup = minmax(h, p, state)
    return coup[0]
score = [0,0,0]
for i in range(50):
    score[partie(strategie_minmax_5, strategie_alea)] += 1
egalite = score[0] / 100

3. Intéraction

3.1. Interaction Joueur

def afficher_grille(state):
    gr = state["grille"]
    for i in range(6):
        s = ""
        for j in range(7):
            if gr[i][j] == 0 :
                s += ". "
            elif gr[i][j] == 1 :
                s += "X "
            else:
                s += "O "
        print(s)
    print("_____________")
    print("0 1 2 3 4 5 6")
 
def strategie_input(state):
    afficher_grille(state)
    col = -1
    while not (0 <= col < 7 and state["libres"][col] > 0):
        col = int(input("Colonne de jeux (0 à 6): "))
    return col

Pour une meilleure experience visuelle on peut redéfinir la fonction partie pour afficher quelques informations supplémentaires dans le jeu.

def partie(stratR, stratJ):
    state = {"grille": [ [0]*7 for i in range(6) ],
             "joueur": 1,
             "libres": [6]*7,
             "vides": 6*7
            }
    strats = (stratR, stratJ)
    while state["vides"] > 0:
        st = strats[state["joueur"]-1]
        coup = st(state)
        if state["joueur"] == 2:
            print("L'IA joue ", coup)
        jouer_coup(state, coup)
        if coup_gagnant(state, coup):
            afficher_grille(state)
            print("Le joueur ", 3-state["joueur"], "a gagné.")
            return
    return 0
 
def play():
    partie(strategie_input, strategie_minmax_5)