sudoto.py

Created by andreanx

Created on December 06, 2023

7.08 KB

Sudoku solver
Usage : sudoto(grid, show, steps) where :
grid = sudoto grid (examples are included : g0, g1, g2 and g3)
show = True for graphic display
steps = True to show steps on graphic display


from kandinsky import *

L_FENETRE, H_FENETRE = 320, 212
POLICE_INFOS = (10,18,1,1) # caracteres numeriques des polices : largeur utile + hauteur utile + marge gauche + marge haute
L_CASE = min((L_FENETRE - 4) // 9, (H_FENETRE - 4) // 9)

def hsv2rgb(h, s=1, v=1):
  h *= 6
  c = v * s
  x = c * (1 - abs((h % 2) - 1))
  r, g, b = h < 1 and (c, x, 0) or h < 2 and (x, c, 0) or h < 3 and (0, c, x) or h < 4 and (0, x, c) or h < 5 and (x, 0, c) or (c, 0, x)
  return [int((k + v - c) * 255) for k in (r, g, b)]

def draw_vert(x, y1, y2, c):
  for y in range(y1, y2): set_pixel(x, y, c)

def draw_horiz(x1, x2, y, c):
  for x in range(x1, x2): set_pixel(x, y, c)

def fill_rect(x, y, l, h, c):
  for dy in range(h):
    for dx in range(l): set_pixel(x + dx, y + dy, c)

def x_col(c, text=False): return POLICE_INFOS[2] + c*L_CASE + c//3 + (text and 1 + (L_CASE - 1 - POLICE_INFOS[0])//2 - POLICE_INFOS[2])
def y_line(l, text=False): return POLICE_INFOS[3] + l*L_CASE + l//3 + (text and 1 + (L_CASE - 1 - POLICE_INFOS[1])//2 - POLICE_INFOS[3])

def clear_case(l, c, back=(255,255,255)):
  x, y = x_col(c) + 1, y_line(l) + 1
  fill_rect(x, y, L_CASE - 1, L_CASE - 1, back)

def draw_case(grille, l, c, col, back=(255,255,255)):
  x, y = x_col(c) + 1, y_line(l) + 1
  if back: clear_case(l, c, back)
  draw_string(str(grille[l][c][0]), x_col(c, True), y_line(l, True), col, back and back or get_pixel(x, y))

def clear_grille(grille):
  for l in range(9):
    for c in range(9):
      if len(grille[l][c]) != 1:
        clear_case(l, c)

def redraw_grille(grille_back, grille, refresh=True):
  for l in range(9):
    for c in range(9):
      if len(grille[l][c]) == 1:
        draw_case(grille, l, c, COLORS[grille[l][c][0]], len(grille_back[l][c]) == 1 and (191, 191, 191) or (255, 255, 255))

def draw_grille(grille_back, grille, refresh=True):
  for l in range(10):
    for c in range(10):
      for i in range(c % 3 == 0 and -1, 1): draw_vert(x_col(c) + i, y_line(0), y_line(9), COLORS[0])
      for i in range(l % 3 == 0 and -1, 1): draw_horiz(x_col(0), x_col(9), y_line(l) + i, COLORS[0])
  redraw_grille(grille_back, grille, refresh)

def ligne(grille, l): return [(l, c) for c in range(9)]
def colonne(grille, c): return [(l, c) for l in range(9)]

def carre(grille, l, c):
  lst = []
  for l2 in range(l - (l % 3), l + 3 - (l % 3)): lst.extend([(l2, c2) for c2 in range(c - (c % 3), c + 3 - (c % 3))])
  return lst

def lst_inclus(l1, l2):
  for v in l1:
    if not(v in l2): return False
  return True

def rechercher_exclusifs(grille, l, c, secteur):
  lc = []
  for (l2, c2) in secteur:
    if lst_inclus(grille[l2][c2], grille[l][c]):
      lc.append((l2, c2))
      if len(lc) == len(grille[l][c]): break
  return lc

def grille_copy(grille): return [[v.copy() for v in l] for l in grille]

def list_grille(grille):
  for l in range(9):
    for c in range(9): grille[l][c] = grille[l][c] == 0 and list(range(1, 10)) or [grille[l][c]]
  return grille

def unlist_grille(grille):
  for l in range(9):
    for c in range(9):
      if len(grille[l][c]) == 1: grille[l][c] = grille[l][c][0]
  return grille

def sudo_remplir(grille, a_tester, show = True):
  global cpt
  lmin, cmin = a_tester[0]
  a_explorer = [lmin, cmin]
  lenmin = 9
  while len(a_tester):
    l, c = a_tester[0]
    a_tester.pop(0)
    if len(grille[l][c]) < 9:
      for secteur in (colonne(grille, c), ligne(grille, l), carre(grille, l, c)):
        # recherche dans le secteur des cases permet de constituer une exclusivite (paires exclusives, triplets exclusifs, etc.)
        lst = rechercher_exclusifs(grille, l, c, secteur)
        if len(lst) == len(grille[l][c]):
          # exclusion des valeurs des autres cases du secteur
          for (l2, c2) in secteur:
            if not((l2, c2) in lst):
              t = False
              for vi in grille[l][c]:
                if vi in grille[l2][c2]:
                  grille[l2][c2].remove(vi)
                  if (l2, c2) in a_explorer:
                    a_explorer.remove((l2, c2))
                  t = True
                if t:
                  if len(grille[l2][c2]) == 1 and show: draw_case(grille, l2, c2, COLORS[grille[l2][c2][0]])
                  cpt[0] += 1
                  if len(grille[l2][c2]) and not ((l2, c2) in a_tester):
                    a_tester.append((l2, c2))
                if len(grille[l2][c2]) != 1 and len(grille[l2][c2]) <= lenmin:
                  if len(grille[l2][c2]) < lenmin:
                    a_explorer = []
                    lmin, cmin, lenmin = l2, c2, len(grille[l2][c2])
                  if not (l2, c2) in a_explorer:
                    a_explorer.append((l2, c2))
                  if len(grille[l2][c2]) == 0: break
        if lenmin == 0: break
  return len(a_explorer) and a_explorer[0] or (lmin, cmin)

COLORS = [(0,0,0)]
COLORS.extend([hsv2rgb(h / 10, 1, 1) for h in range(10)])

def sudoto_rec(grille_back, grille, a_tester, show = True):
  global cpt
  l, c = sudo_remplir(grille, a_tester, show)
  lastlen = len(grille[l][c])
  if lastlen <= 1:
    return lastlen == 1
  else:
    if show: draw_grille(grille_back, grille, False)
    for v in grille[l][c]:
      grille_copie = grille_copy(grille)
      grille_copie[l][c] = [v]
      cpt[1] += 1
      if show: draw_case(grille_copie, l, c, COLORS[v], (0, 0, 0))
      t = sudoto_rec(grille_back, grille_copie, [(l, c)], show)
      if t:
        for l in range(9): grille[l] = grille_copie[l]
        return True
      if show: clear_case(l, c)
    return False

def sudoto(grille, show = True, steps = True):
  global cpt
  cpt = [0, 0]
  grille = list_grille(grille)
  grille_back = grille_copy(grille)
  if show: draw_grille(grille_back, grille)
  a_tester = []
  for l in range(9):
    for c in range(9):
      if len(grille[l][c]) < 9: a_tester.append((l, c))
  t = sudoto_rec(grille_back, grille, a_tester, show and steps)
  if show and not steps: draw_grille(grille_back, grille, False)
  grille = unlist_grille(grille)
  print((t and "succes" or "echec") + "\n{:d} etape{:s} dont :\n  {:d} deduction{:s}\n  {:d} pari{:s}".format(cpt[0] + cpt[1], cpt[0] + cpt[1] > 1 and "s" or "", cpt[0], cpt[0] > 1 and "s" or "", cpt[1], cpt[1] > 1 and "s" or ""))
  for l in range(9): print(grille[l])

g1=[[0,1,6,7,8,0,3,0,0], # Casio Paques 1
    [0,0,7,0,4,0,9,6,8],
    [0,0,0,0,2,6,1,0,0],
    [6,2,0,0,7,0,0,3,0],
    [0,7,0,0,5,0,0,9,0],
    [0,5,0,0,9,0,0,7,1],
    [0,0,5,2,3,0,0,0,0],
    [1,4,2,0,6,0,7,0,0],
    [0,0,3,0,1,4,2,8,0]]
g2=[[9,0,0,1,0,0,0,0,5],
    [0,0,5,0,9,0,2,0,1],
    [8,0,0,0,4,0,0,0,0],
    [0,0,0,0,8,0,0,0,0],
    [0,0,0,7,0,0,0,0,0],
    [0,0,0,0,2,6,0,0,9],
    [2,0,0,3,0,0,0,0,6],
    [0,0,0,2,0,0,9,0,0],
    [0,0,1,9,0,4,5,7,0]]
g0=[[1,0,0,0,0,7,0,9,0], # Al Escargot
    [0,3,0,0,2,0,0,0,8],
    [0,0,9,6,0,0,5,0,0],
    [0,0,5,3,0,0,9,0,0],
    [0,1,0,0,8,0,0,0,2],
    [6,0,0,0,0,4,0,0,0],
    [3,0,0,0,0,0,0,1,0],
    [0,4,0,0,0,0,0,0,7],
    [0,0,7,0,0,0,3,0,0]]
g3=[[8,0,0,0,0,0,0,0,0], # NumWorks Decembre 2023
    [0,0,3,6,0,0,0,0,0],
    [0,7,0,0,9,0,2,0,0],
    [0,5,0,0,0,7,0,0,0],
    [0,0,0,0,4,5,7,0,0],
    [0,0,0,1,0,0,0,3,0],
    [0,0,1,0,0,0,0,6,8],
    [0,0,8,5,0,0,0,1,0],
    [0,9,0,0,0,0,4,0,0]]
sudoto(g2, True, True)

During your visit to our site, NumWorks needs to install "cookies" or use other technologies to collect data about you in order to:

With the exception of Cookies essential to the operation of the site, NumWorks leaves you the choice: you can accept Cookies for audience measurement by clicking on the "Accept and continue" button, or refuse these Cookies by clicking on the "Continue without accepting" button or by continuing your browsing. You can update your choice at any time by clicking on the link "Manage my cookies" at the bottom of the page. For more information, please consult our <a href="https://www.numworks.com/legal/cookies-policy/">cookies policy</a>.