here2share

# tk_5x5_Sudoku_Slider.py

Oct 27th, 2025
1,262
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.52 KB | None | 0 0
  1. # tk_5x5_Sudoku_Slider.py
  2.  
  3. from tkinter import *
  4. from PIL import Image, ImageDraw, ImageFont, ImageTk
  5. from random import randint
  6. import itertools
  7.  
  8. BACKGROUND = 'orange'
  9. TSIZE = 90
  10. MARGIN = 7
  11. ROWS = 5
  12. COLUMNS = 5
  13. board = {}
  14.  
  15. colors = ['#ff8888', '#8888ff', '#88ff88', '#ffff66']
  16. ARC_COLORS = list(itertools.permutations(colors, 4))
  17.  
  18. instructions = '''
  19. # tk_5x5_Sudoku_Slider.py
  20.  
  21. To win this game, the player must satisfy all the following conditions:
  22.  
  23. 1. Valid 5×5 Sudoku Grid
  24. - Each number from 1 to 5 must appear exactly once in each row and each column.
  25.  
  26. 2. Matching Edge Colors
  27. - The top half-circle of a tile must match the bottom half-circle of the tile above it.
  28. - The left half-circle must match the right half-circle of the tile to its left.
  29. '''
  30.  
  31. def create_tile_image(n, arc_colors_4, idx):
  32.     img_size = TSIZE + 2 * MARGIN
  33.     img = Image.new('RGBA', (img_size, img_size))
  34.     draw = ImageDraw.Draw(img)
  35.  
  36.     draw.rectangle([MARGIN, MARGIN, img_size - MARGIN, img_size - MARGIN], outline=None, fill='white')
  37.  
  38.     r = TSIZE - 50
  39.     cx, cy = img_size // 2, img_size // 2
  40.  
  41.     draw.pieslice([cx - r//2, MARGIN - r//2, cx + r//2, MARGIN + r//2], start=0, end=180, fill=arc_colors_4[0])
  42.     draw.pieslice([cx - r//2, img_size - MARGIN - r//2, cx + r//2, img_size - MARGIN + r//2], start=180, end=360, fill=arc_colors_4[1])
  43.     draw.pieslice([MARGIN - r//2, cy - r//2, MARGIN + r//2, cy + r//2], start=270, end=90, fill=arc_colors_4[2])
  44.     draw.pieslice([img_size - MARGIN - r//2, cy - r//2, img_size - MARGIN + r//2, cy + r//2], start=90, end=270, fill=arc_colors_4[3])
  45.  
  46.     font = ImageFont.truetype("arial.ttf", int(TSIZE * 0.7))
  47.     text_size = draw.textsize(str(n), font=font)
  48.     text_pos = (cx - text_size[0] // 2, cy - text_size[1] // 2 - 6)
  49.     draw.text(text_pos, str(n), font=font, fill='black')
  50.    
  51.     #draw.text(text_pos, str(idx), font=font, fill='gray') # testing purpose
  52.  
  53.     draw.rectangle([MARGIN, MARGIN, img_size - MARGIN, img_size - MARGIN], outline='black', fill=None)
  54.  
  55.     return ImageTk.PhotoImage(img)
  56.    
  57. def generate_latin_square(n=5):
  58.     import random
  59.     base = list(range(1, n + 1))
  60.     square = []
  61.     for i in range(n):
  62.         row = base[i:] + base[:i]
  63.         square.append(row)
  64.  
  65.     for _ in range(10):
  66.         i, j = random.sample(range(n), 2)
  67.         square[i], square[j] = square[j], square[i]
  68.         for row in square:
  69.             row[i], row[j] = row[j], row[i]
  70.     return square
  71.  
  72. def permute(array):
  73.     n = len(array)
  74.     signature = +1
  75.     for i in range(n - 2):
  76.         j = randint(0, n - 1)
  77.         if j != i:
  78.             signature = -signature
  79.             array[i], array[j] = array[j], array[i]
  80.     if signature < 0:
  81.         array[-2], array[-1] = array[-1], array[-2]
  82.  
  83. def create_tile(idx, arc_colors_4, n):
  84.     image = create_tile_image(n, arc_colors_4, idx)
  85.     tile_id = canvas.create_image(0, 0, image=image, anchor='nw')
  86.     return {'id': idx, 'canvas_id': tile_id, 'image': image, 'row': 0, 'column': 0}
  87.  
  88. def move_tile(tile, dx, dy):
  89.     canvas.move(tile['canvas_id'], dx, dy)
  90.  
  91. def moveto_tile(tile, row, column):
  92.     S = MARGIN + TSIZE
  93.     x = MARGIN + column * S
  94.     y = MARGIN + row * S
  95.     current_x = MARGIN + tile['column'] * S
  96.     current_y = MARGIN + tile['row'] * S
  97.     dx = x - current_x
  98.     dy = y - current_y
  99.     move_tile(tile, dx, dy)
  100.     tile['row'] = row
  101.     tile['column'] = column
  102.  
  103. def create_timer(parent):
  104.     label = Label(parent)
  105.     label.elapsed = 0
  106.     label.config(text='Time: 0s')
  107.     label.running = True
  108.     def update():
  109.         if label.running:
  110.             label.elapsed += 1
  111.             label.config(text=f'Time: {label.elapsed}s')
  112.             label.after_id = label.after(1000, update)
  113.     label.after_id = label.after(1000, update)
  114.     return label
  115.  
  116. def stop_timer():
  117.     timer = board['timer']
  118.     timer.running = False
  119.     if hasattr(timer, 'after_id'):
  120.         timer.after_cancel(timer.after_id)
  121.  
  122. def reset_timer():
  123.     timer = board['timer']
  124.     timer.running = False
  125.     if hasattr(timer, 'after_id'):
  126.         timer.after_cancel(timer.after_id)
  127.     timer.elapsed = 0
  128.     timer.config(text='Time: 0s')
  129.     timer.running = True
  130.     def update():
  131.         if timer.running:
  132.             timer.elapsed += 1
  133.             timer.config(text=f'Time: {timer.elapsed}s')
  134.             timer.after_id = timer.after(1000, update)
  135.     timer.after_id = timer.after(1000, update)
  136.  
  137. def assign_matching_combos(rows, columns):
  138.     grid = [[None for _ in range(columns)] for _ in range(rows)]
  139.     used = set()
  140.  
  141.     def is_valid(combo, r, c):
  142.         if r > 0 and grid[r - 1][c] and grid[r - 1][c][1] != combo[0]:
  143.             return False
  144.         if c > 0 and grid[r][c - 1] and grid[r][c - 1][3] != combo[2]:
  145.             return False
  146.         return True
  147.  
  148.     def backtrack(r, c):
  149.         if r == rows:
  150.             return True
  151.  
  152.         next_r, next_c = (r, c + 1) if c + 1 < columns else (r + 1, 0)
  153.  
  154.         if r == rows - 1 and c == columns - 1:
  155.             return backtrack(next_r, next_c)
  156.  
  157.         for combo in ARC_COLORS:
  158.             if combo in used:
  159.                 continue
  160.             if is_valid(combo, r, c):
  161.                 grid[r][c] = combo
  162.                 used.add(combo)
  163.                 if backtrack(next_r, next_c):
  164.                     return True
  165.                 grid[r][c] = None
  166.                 used.remove(combo)
  167.  
  168.         return False
  169.  
  170.     backtrack(0, 0)
  171.  
  172.     return grid
  173.  
  174. def create_board(timer, rows, columns):
  175.     combo_grid = assign_matching_combos(rows, columns)
  176.     latin_square = generate_latin_square(rows)
  177.     tiles = []
  178.     for r in range(rows):
  179.         for c in range(columns):
  180.             if r == rows - 1 and c == columns - 1:
  181.                 continue
  182.             arc_colors_4 = combo_grid[r][c]
  183.             number = latin_square[r][c]
  184.             tile = create_tile(len(tiles) + 1, arc_colors_4, number)
  185.             moveto_tile(tile, r, c)
  186.             tiles.append(tile)
  187.     board['tiles'] = tiles
  188.     board['rows'] = rows
  189.     board['columns'] = columns
  190.     board['row'] = rows - 1
  191.     board['col'] = columns - 1
  192.     board['timer'] = timer
  193.     board['locked'] = False
  194.     canvas.bind('<Button-1>', lambda e: clickhandler(e))
  195.     canvas.pack()
  196.     shuffle_board()
  197.  
  198. def shuffle_board():
  199.     canvas.delete('end')
  200.     board['locked'] = False
  201.     positions = [(r, c) for r in range(ROWS) for c in range(COLUMNS)]
  202.     positions.remove((ROWS - 1, COLUMNS - 1))
  203.     permute(positions)
  204.     for tile, (r, c) in zip(board['tiles'], positions):
  205.         moveto_tile(tile, r, c)
  206.     board['row'] = ROWS - 1
  207.     board['col'] = COLUMNS - 1
  208.     reset_timer()
  209.  
  210. def update_timer():
  211.     timer = board['timer']
  212.     if timer.running:
  213.         timer.elapsed += 1
  214.         timer.config(text=f'Time: {timer.elapsed}s')
  215.         timer.after_id = timer.after(1000, lambda: update_timer())
  216.  
  217. def clickhandler(event):
  218.     if board['locked']:
  219.         return
  220.     S = MARGIN + TSIZE
  221.     row = max(0, min((event.y - MARGIN // 2) // S, board['rows'] - 1))
  222.     column = max(0, min((event.x - MARGIN // 2) // S, board['columns'] - 1))
  223.     if row == board['row'] and column != board['col']:
  224.         move_row(row, column)
  225.     elif column == board['col'] and row != board['row']:
  226.         move_column(row, column)
  227.     check_win()
  228.  
  229. def move_row(r, c):
  230.     delta = 1 if c > board['col'] else -1
  231.     for col in range(board['col'], c, delta):
  232.         move_tile_at(r, col + delta, 0, -delta)
  233.     board['col'] = c
  234.  
  235. def move_column(r, c):
  236.     delta = 1 if r > board['row'] else -1
  237.     for row in range(board['row'], r, delta):
  238.         move_tile_at(row + delta, c, -delta, 0)
  239.     board['row'] = r
  240.  
  241. def move_tile_at(r, c, tr, tc):
  242.     for tile in board['tiles']:
  243.         if tile['row'] == r and tile['column'] == c:
  244.             moveto_tile(tile, r + tr, c + tc)
  245.             break
  246.  
  247. def check_win():
  248.     for tile in board['tiles']:
  249.         expected_id = tile['row'] * board['columns'] + tile['column'] + 1
  250.         if tile['id'] != expected_id:
  251.             return
  252.  
  253.     stop_timer()
  254.     board['locked'] = True
  255.     S = MARGIN + TSIZE
  256.     canvas.create_rectangle(0, 40, S * ROWS + MARGIN, 140, fill='gray', stipple='gray75', outline='', tag='end')
  257.     canvas.create_text(canvas.winfo_width() // 2, TSIZE - 5, text="Puzzle Complete!", font='Helvetica 40', fill='white', tag='end')
  258.  
  259. def change_bg(color):
  260.     canvas['background'] = color
  261.  
  262. def show_about():
  263.     top = Toplevel(root)
  264.     top.title("Program Info")
  265.     Label(top, text=instructions, padx=20, pady=20).pack()
  266.     Button(top, text="OK", command=top.destroy).pack()
  267.  
  268. root = Tk()
  269. root.geometry('+0+0')
  270. root.title("# tk_5x5_Sudoku_Slider.py")
  271.  
  272. S = MARGIN + TSIZE
  273. canvas = Canvas(root, background=BACKGROUND, height=S * ROWS + MARGIN - 2, width=S * COLUMNS + MARGIN - 2)
  274.  
  275. menubar = Frame(root, relief=RAISED, bd=2)
  276. menubutton1 = Menubutton(menubar, text='Options')
  277. menubutton1.pack(side=LEFT)
  278. menubutton2 = Menubutton(menubar, text='Colors')
  279. menubutton2.pack(side=LEFT)
  280. timer = create_timer(menubar)
  281. timer.pack(side=RIGHT, padx=MARGIN)
  282. menubar.pack(side=TOP, fill=X)
  283. create_board(timer, ROWS, COLUMNS)
  284.  
  285. menu1 = Menu(menubutton1)
  286. menu1.add_command(label='About this program...', command=show_about)
  287. menu1.add_command(label='New game', command=lambda: shuffle_board())
  288. menu1.add_command(label='Quit', command=root.quit)
  289. menubutton1['menu'] = menu1
  290.  
  291. menu2 = Menu(menubutton2)
  292. menu2.add_command(label='Burlywood', command=lambda: change_bg('burlywood'))
  293. menu2.add_command(label='Forest Green', command=lambda: change_bg('Forest Green'))
  294. menu2.add_command(label='Orange', command=lambda: change_bg('orange'))
  295. menubutton2['menu'] = menu2
  296.  
  297. root.mainloop()
Advertisement
Add Comment
Please, Sign In to add comment