Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # tk_5x5_Sudoku_Slider.py
- from tkinter import *
- from PIL import Image, ImageDraw, ImageFont, ImageTk
- from random import randint
- import itertools
- BACKGROUND = 'orange'
- TSIZE = 90
- MARGIN = 7
- ROWS = 5
- COLUMNS = 5
- board = {}
- colors = ['#ff8888', '#8888ff', '#88ff88', '#ffff66']
- ARC_COLORS = list(itertools.permutations(colors, 4))
- instructions = '''
- # tk_5x5_Sudoku_Slider.py
- To win this game, the player must satisfy all the following conditions:
- 1. Valid 5×5 Sudoku Grid
- - Each number from 1 to 5 must appear exactly once in each row and each column.
- 2. Matching Edge Colors
- - The top half-circle of a tile must match the bottom half-circle of the tile above it.
- - The left half-circle must match the right half-circle of the tile to its left.
- '''
- def create_tile_image(n, arc_colors_4, idx):
- img_size = TSIZE + 2 * MARGIN
- img = Image.new('RGBA', (img_size, img_size))
- draw = ImageDraw.Draw(img)
- draw.rectangle([MARGIN, MARGIN, img_size - MARGIN, img_size - MARGIN], outline=None, fill='white')
- r = TSIZE - 50
- cx, cy = img_size // 2, img_size // 2
- draw.pieslice([cx - r//2, MARGIN - r//2, cx + r//2, MARGIN + r//2], start=0, end=180, fill=arc_colors_4[0])
- 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])
- draw.pieslice([MARGIN - r//2, cy - r//2, MARGIN + r//2, cy + r//2], start=270, end=90, fill=arc_colors_4[2])
- 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])
- font = ImageFont.truetype("arial.ttf", int(TSIZE * 0.7))
- text_size = draw.textsize(str(n), font=font)
- text_pos = (cx - text_size[0] // 2, cy - text_size[1] // 2 - 6)
- draw.text(text_pos, str(n), font=font, fill='black')
- #draw.text(text_pos, str(idx), font=font, fill='gray') # testing purpose
- draw.rectangle([MARGIN, MARGIN, img_size - MARGIN, img_size - MARGIN], outline='black', fill=None)
- return ImageTk.PhotoImage(img)
- def generate_latin_square(n=5):
- import random
- base = list(range(1, n + 1))
- square = []
- for i in range(n):
- row = base[i:] + base[:i]
- square.append(row)
- for _ in range(10):
- i, j = random.sample(range(n), 2)
- square[i], square[j] = square[j], square[i]
- for row in square:
- row[i], row[j] = row[j], row[i]
- return square
- def permute(array):
- n = len(array)
- signature = +1
- for i in range(n - 2):
- j = randint(0, n - 1)
- if j != i:
- signature = -signature
- array[i], array[j] = array[j], array[i]
- if signature < 0:
- array[-2], array[-1] = array[-1], array[-2]
- def create_tile(idx, arc_colors_4, n):
- image = create_tile_image(n, arc_colors_4, idx)
- tile_id = canvas.create_image(0, 0, image=image, anchor='nw')
- return {'id': idx, 'canvas_id': tile_id, 'image': image, 'row': 0, 'column': 0}
- def move_tile(tile, dx, dy):
- canvas.move(tile['canvas_id'], dx, dy)
- def moveto_tile(tile, row, column):
- S = MARGIN + TSIZE
- x = MARGIN + column * S
- y = MARGIN + row * S
- current_x = MARGIN + tile['column'] * S
- current_y = MARGIN + tile['row'] * S
- dx = x - current_x
- dy = y - current_y
- move_tile(tile, dx, dy)
- tile['row'] = row
- tile['column'] = column
- def create_timer(parent):
- label = Label(parent)
- label.elapsed = 0
- label.config(text='Time: 0s')
- label.running = True
- def update():
- if label.running:
- label.elapsed += 1
- label.config(text=f'Time: {label.elapsed}s')
- label.after_id = label.after(1000, update)
- label.after_id = label.after(1000, update)
- return label
- def stop_timer():
- timer = board['timer']
- timer.running = False
- if hasattr(timer, 'after_id'):
- timer.after_cancel(timer.after_id)
- def reset_timer():
- timer = board['timer']
- timer.running = False
- if hasattr(timer, 'after_id'):
- timer.after_cancel(timer.after_id)
- timer.elapsed = 0
- timer.config(text='Time: 0s')
- timer.running = True
- def update():
- if timer.running:
- timer.elapsed += 1
- timer.config(text=f'Time: {timer.elapsed}s')
- timer.after_id = timer.after(1000, update)
- timer.after_id = timer.after(1000, update)
- def assign_matching_combos(rows, columns):
- grid = [[None for _ in range(columns)] for _ in range(rows)]
- used = set()
- def is_valid(combo, r, c):
- if r > 0 and grid[r - 1][c] and grid[r - 1][c][1] != combo[0]:
- return False
- if c > 0 and grid[r][c - 1] and grid[r][c - 1][3] != combo[2]:
- return False
- return True
- def backtrack(r, c):
- if r == rows:
- return True
- next_r, next_c = (r, c + 1) if c + 1 < columns else (r + 1, 0)
- if r == rows - 1 and c == columns - 1:
- return backtrack(next_r, next_c)
- for combo in ARC_COLORS:
- if combo in used:
- continue
- if is_valid(combo, r, c):
- grid[r][c] = combo
- used.add(combo)
- if backtrack(next_r, next_c):
- return True
- grid[r][c] = None
- used.remove(combo)
- return False
- backtrack(0, 0)
- return grid
- def create_board(timer, rows, columns):
- combo_grid = assign_matching_combos(rows, columns)
- latin_square = generate_latin_square(rows)
- tiles = []
- for r in range(rows):
- for c in range(columns):
- if r == rows - 1 and c == columns - 1:
- continue
- arc_colors_4 = combo_grid[r][c]
- number = latin_square[r][c]
- tile = create_tile(len(tiles) + 1, arc_colors_4, number)
- moveto_tile(tile, r, c)
- tiles.append(tile)
- board['tiles'] = tiles
- board['rows'] = rows
- board['columns'] = columns
- board['row'] = rows - 1
- board['col'] = columns - 1
- board['timer'] = timer
- board['locked'] = False
- canvas.bind('<Button-1>', lambda e: clickhandler(e))
- canvas.pack()
- shuffle_board()
- def shuffle_board():
- canvas.delete('end')
- board['locked'] = False
- positions = [(r, c) for r in range(ROWS) for c in range(COLUMNS)]
- positions.remove((ROWS - 1, COLUMNS - 1))
- permute(positions)
- for tile, (r, c) in zip(board['tiles'], positions):
- moveto_tile(tile, r, c)
- board['row'] = ROWS - 1
- board['col'] = COLUMNS - 1
- reset_timer()
- def update_timer():
- timer = board['timer']
- if timer.running:
- timer.elapsed += 1
- timer.config(text=f'Time: {timer.elapsed}s')
- timer.after_id = timer.after(1000, lambda: update_timer())
- def clickhandler(event):
- if board['locked']:
- return
- S = MARGIN + TSIZE
- row = max(0, min((event.y - MARGIN // 2) // S, board['rows'] - 1))
- column = max(0, min((event.x - MARGIN // 2) // S, board['columns'] - 1))
- if row == board['row'] and column != board['col']:
- move_row(row, column)
- elif column == board['col'] and row != board['row']:
- move_column(row, column)
- check_win()
- def move_row(r, c):
- delta = 1 if c > board['col'] else -1
- for col in range(board['col'], c, delta):
- move_tile_at(r, col + delta, 0, -delta)
- board['col'] = c
- def move_column(r, c):
- delta = 1 if r > board['row'] else -1
- for row in range(board['row'], r, delta):
- move_tile_at(row + delta, c, -delta, 0)
- board['row'] = r
- def move_tile_at(r, c, tr, tc):
- for tile in board['tiles']:
- if tile['row'] == r and tile['column'] == c:
- moveto_tile(tile, r + tr, c + tc)
- break
- def check_win():
- for tile in board['tiles']:
- expected_id = tile['row'] * board['columns'] + tile['column'] + 1
- if tile['id'] != expected_id:
- return
- stop_timer()
- board['locked'] = True
- S = MARGIN + TSIZE
- canvas.create_rectangle(0, 40, S * ROWS + MARGIN, 140, fill='gray', stipple='gray75', outline='', tag='end')
- canvas.create_text(canvas.winfo_width() // 2, TSIZE - 5, text="Puzzle Complete!", font='Helvetica 40', fill='white', tag='end')
- def change_bg(color):
- canvas['background'] = color
- def show_about():
- top = Toplevel(root)
- top.title("Program Info")
- Label(top, text=instructions, padx=20, pady=20).pack()
- Button(top, text="OK", command=top.destroy).pack()
- root = Tk()
- root.geometry('+0+0')
- root.title("# tk_5x5_Sudoku_Slider.py")
- S = MARGIN + TSIZE
- canvas = Canvas(root, background=BACKGROUND, height=S * ROWS + MARGIN - 2, width=S * COLUMNS + MARGIN - 2)
- menubar = Frame(root, relief=RAISED, bd=2)
- menubutton1 = Menubutton(menubar, text='Options')
- menubutton1.pack(side=LEFT)
- menubutton2 = Menubutton(menubar, text='Colors')
- menubutton2.pack(side=LEFT)
- timer = create_timer(menubar)
- timer.pack(side=RIGHT, padx=MARGIN)
- menubar.pack(side=TOP, fill=X)
- create_board(timer, ROWS, COLUMNS)
- menu1 = Menu(menubutton1)
- menu1.add_command(label='About this program...', command=show_about)
- menu1.add_command(label='New game', command=lambda: shuffle_board())
- menu1.add_command(label='Quit', command=root.quit)
- menubutton1['menu'] = menu1
- menu2 = Menu(menubutton2)
- menu2.add_command(label='Burlywood', command=lambda: change_bg('burlywood'))
- menu2.add_command(label='Forest Green', command=lambda: change_bg('Forest Green'))
- menu2.add_command(label='Orange', command=lambda: change_bg('orange'))
- menubutton2['menu'] = menu2
- root.mainloop()
Advertisement
Add Comment
Please, Sign In to add comment