import win32api import win32con import win32gui import pygame import ctypes import json from tkinter import Tk, filedialog import numpy as np # INITIALIZATION pygame.init() FPS = 60 MAIN_PATH = "C:/Users/User/Documents/Coding/Picture_Puzzle/" # School: MAIN_PATH = "C:/Users/RVKG/Documents/My Palettes/Picture Puzzle/" IMAGES_PATH = "images/albums/" SIZE_PATH = "images/assets/" DATA_FILE = "main_data.json" PUZZLE_FILE = "main_puzzle.json" SETTINGS_FILE = "main_settings.json" STATS_FILE = "main_stats.json" black = (0, 0, 0) white = (255, 255, 255) transparent = (255, 0, 128) # Data loading and saving def load_data(data): try: with open(data, "r") as file: current_data = json.load(file) except (FileNotFoundError, json.JSONDecodeError): print("Something went wrong with file loading") return current_data storage = load_data(DATA_FILE) puzzle_data = load_data(PUZZLE_FILE) settings_data = load_data(SETTINGS_FILE) stats_data = load_data(STATS_FILE) # Configuration SIZE_CHOICES = ["small", "medium", "big"] STYLE_CHOICES = ["classic", "original", "dark"] GRID_CHOICES = ["4x4", "3x3"] MUSIC_CHOICES = ["funky-leap", "serenity", "sunny-day"] SOUND_CHOICES = ["puzzle", "wood", "metal_pipe", "lego_breaking"] DISPLAY_CHOICES = ["time", "moves"] LANGUAGE_CHOICES = ["english", "russian", "latvian"] FONTS = ["msreferencesansserif", "arial", "bahnschrift"] if settings_data["style"] == STYLE_CHOICES[0]: FONT = FONTS[0] STYLE_PATH = "1_" text_color = (1, 35, 61) oposite_color = white elif settings_data["style"] == STYLE_CHOICES[1]: FONT = FONTS[1] STYLE_PATH = "2_" text_color = white oposite_color = (70, 35, 0) else: FONT = FONTS[2] STYLE_PATH = "3_" text_color = white oposite_color = text_color if settings_data["size"] == SIZE_CHOICES[0]: WIDTH, HEIGHT = 144, 154 INT = 0 if FONT == FONTS[0]: FONT_SIZE = 11 FONT_SIZE2 = 10 elif FONT == FONTS[1]: FONT_SIZE = 12 FONT_SIZE2 = 11 elif FONT == FONTS[2]: FONT_SIZE = 11 FONT_SIZE2 = 10 puzzle_size = (128, 128) non_movable_area = pygame.Rect(8, 19, 128, 128) timer_area = pygame.Rect(22, 1, 55, 12) win_area = pygame.Rect(74, 78, 128, 128) size = [20, 25, 116, 15] cons = [15, 195, 12, 3, 9] scaled_size = (18, 21, 108, 108) area = (72, 142) elif settings_data["size"] == SIZE_CHOICES[1]: WIDTH, HEIGHT = 202, 216 INT = 1 if FONT == FONTS[0]: FONT_SIZE = 15 FONT_SIZE2 = 13 elif FONT == FONTS[1]: FONT_SIZE = 17 FONT_SIZE2 = 15 elif FONT == FONTS[2]: FONT_SIZE = 16 FONT_SIZE2 = 14 puzzle_size = (180, 180) non_movable_area = pygame.Rect(11, 26, 180, 180) timer_area = pygame.Rect(27, 2, 86, 16) win_area = pygame.Rect(104, 108, 180, 180) size = [30, 35, 161, 25] cons = [25, 247, 18, 3, 13] scaled_size = (22, 26, 158, 158) area = (100, 200) else: WIDTH, HEIGHT = 396, 425 INT = 2 if FONT == FONTS[0]: FONT_SIZE = 24 FONT_SIZE2 = 21 elif FONT == FONTS[1]: FONT_SIZE = 22 FONT_SIZE2 = 19 elif FONT == FONTS[2]: FONT_SIZE = 21 FONT_SIZE2 = 18 puzzle_size = (360, 360) non_movable_area = pygame.Rect(18, 48, 360, 360) timer_area = pygame.Rect(49, 5, 86, 32) win_area = pygame.Rect(200, 214, 360, 360) size = [50, 55, 328, 35] cons = [35, 455, 25, 13, 26] scaled_size = (43, 48, 310, 310) area = (197, 390) puzzle_version = settings_data["grid"] # Seting screen and other stuff programIcon = pygame.image.load('icon.png') pygame.display.set_icon(programIcon) pygame.display.set_caption('Picture Puzzle') screen_info = pygame.display.Info() window_coords = [(screen_info.current_w - WIDTH) // 2, (screen_info.current_h - HEIGHT) // 2] screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.NOFRAME) clock = pygame.time.Clock() hwnd = pygame.display.get_wm_info()["window"] # Sprites generation class SpriteSheet: def __init__(self, sheet_path): self.sheet = pygame.image.load(sheet_path).convert_alpha() def get_sprite(self, position, width, height, color_key=None): image = pygame.Surface((width, height), pygame.SRCALPHA).convert_alpha() image.blit(self.sheet, (0, 0), (position[0], position[1], width, height)) if color_key: for x in range(width): for y in range(height): if image.get_at((x, y))[:3] == color_key: image.set_at((x, y), (0, 0, 0, 0)) return image def set_hue(self, hue): for x in range(self.sheet.get_width()): for y in range(self.sheet.get_height()): color = self.sheet.get_at((x, y)) h, s, v, a = pygame.Color(color) h = (h + hue) % 360 new_color = pygame.Color(0, 0, 0, 0) new_color.hsva = (h, s, v, a) self.sheet.set_at((x, y), new_color) sprite_sheet = SpriteSheet(SIZE_PATH + str(INT) + STYLE_PATH + "sprites.png") class Image: def __init__(self, sprite_position, window_position, size): self.sprite_sheet = sprite_sheet self.sprite_position = sprite_position self.window_position = window_position self.size = size self.update_image() def update_image(self): self.img = self.sprite_sheet.get_sprite(self.sprite_position, *self.size) self.rect = self.img.get_rect(topleft=self.window_position) def draw(self, screen): screen.blit(self.img, self.rect) class Button: def __init__(self, sprite_position, window_position, size): self.sprite_sheet = sprite_sheet self.sprite_position = sprite_position self.window_position = window_position self.size = size self.hovered = False self.disabled = False self.update_images() def update_images(self): self.img = self.sprite_sheet.get_sprite(self.sprite_position, *self.size) hover_position = ( self.sprite_position[0], self.sprite_position[1] + 3 + self.size[1] ) self.hover_image = self.sprite_sheet.get_sprite(hover_position, *self.size) self.rect = self.img.get_rect(topleft=self.window_position) self.mask = pygame.mask.from_surface(self.img) def is_hovered(self, mouse_pos): return self.rect.collidepoint(mouse_pos) and self.mask.get_at( (mouse_pos[0] - self.rect.x, mouse_pos[1] - self.rect.y) ) def disable(self): self.disabled = True def enable(self): self.disabled = False def update(self, mouse_pos): self.hovered = self.is_hovered(mouse_pos) def draw(self, screen): if not self.disabled: screen.blit(self.hover_image if self.hovered else self.img, self.rect) else: screen.blit(self.img, self.rect) class Slider: def __init__(self, position, value_range, aspect): self.second_x= 80*aspect self.slider_pos = self.handle_event() self.first_line = Image((135, 38), position, (80, 12)) self.second_line = Image((135, 53), position, (self.second_x, 12)) self.slider_button = Button((218, 38), self.slider_pos, (10, 16)) self.position = position self.value_range = value_range self.value = value_range[0] self.dragging = False def handle_event(self): self.second_x self.slider_pos[0] = self.position[0] #not really, 1. it takes value_range and current value, 2. it has to be movable only in slider x rect self.slider_pos[1] = self.position[1]+2 self.dragging = True mouse_x = max(self.white_line.rect.x, min(event.pos[0], self.white_line.rect.x + self.white_line.rect.width)) value = ((mouse_x - self.white_line.rect.x) / (self.white_line.rect.width - self.slider_button.rect.width)) * 360 self.white_line.update_value(value) self.blue_line.update_value(value) self.slider_button.rect.x = mouse_x - self.slider_button.rect.width // 2 return self.slider_pos def update_position(self, position): self.position = position self.white_line.position = position self.blue_line.position = position self.slider_button.window_position = (position[0], position[1] + (self.slider_button.size[1] - self.white_line.rect.height) // 2) def blit(self, screen): screen.blit(self.white_line.img, self.white_line.rect) screen.blit(self.blue_line.img, self.blue_line.rect) self.slider_button.draw(screen) # Ability to move window def move_win(coordinates): hwnd = pygame.display.get_wm_info()["window"] w, h = pygame.display.get_surface().get_size() win32gui.MoveWindow(hwnd, coordinates[0], coordinates[1], w, h, True) set_shadow_style(hwnd) # Generating shadow def set_shadow_style(hwnd): try: DWMWA_NCRENDERING_POLICY = 2 DWMNCRP_ENABLED = 2 ctypes.windll.dwmapi.DwmSetWindowAttribute( hwnd, DWMWA_NCRENDERING_POLICY, ctypes.byref(ctypes.c_uint(DWMNCRP_ENABLED)), ctypes.sizeof(ctypes.c_uint), ) MARGINS = (ctypes.c_int * 4)(-1, -1, -1, -1) ctypes.windll.dwmapi.DwmExtendFrameIntoClientArea(hwnd, ctypes.byref(MARGINS)) except Exception as e: print(e) # Text render def render_text(text, position, screen, color=text_color, centered=False): font = pygame.font.SysFont(FONT, FONT_SIZE) text_render = font.render(text, True, color) if centered: text_rect = text_render.get_rect(center=position) screen.blit(text_render, text_rect.topleft) else: screen.blit(text_render, position) def render_settings_text(text, value, position, screen, color=text_color, centered=True): font = pygame.font.SysFont(FONT, FONT_SIZE2-1) text_render = font.render(f"{text}: {value}", True, color) if centered: text_rect = text_render.get_rect(center=position) screen.blit(text_render, text_rect.topleft) else: screen.blit(text_render, position) # MENU MODE def browse(): root = Tk() root.withdraw() file_path = filedialog.askopenfilename(filetypes=[("Image files", "*.png;*.jpg;*.jpeg;*.gif")]) root.destroy() return file_path quick_game_activated = False selected_file = None def quick_game(): global selected_file, puzzle_image, puzzle_pieces, shuffled_pieces, empty_position, quick_game_activated, original_image quick_game_activated = True try: selected_file = browse() if selected_file is not None: puzzle_image = pygame.image.load(selected_file) original_image = pygame.transform.scale(puzzle_image, puzzle_size) puzzle_pieces = cut_image(original_image) shuffled_pieces = shuffle_pieces(puzzle_pieces) empty_position = (len(puzzle_matrix) - 1, len(puzzle_matrix[0]) - 1) quick_game_activated = False except FileNotFoundError: print("No file selected") def render_albums(): global current_mode, album_name current_hovered_index = -1 size_to_y = {"small": 25, "medium": 35, "big": 65} y = size_to_y[settings_data["size"]] for hover_index, album in enumerate(storage["albums"]): render_text(album, (size[1], y), screen) album_rect = pygame.Rect(size[0], y, size[2], size[3]) album_hover = album_rect.collidepoint(pygame.mouse.get_pos()) if album_hover: current_hovered_index = hover_index if album_hover and pygame.mouse.get_pressed()[0]: album_name = album if album_name != album: album_name = None if album_name is not None: current_mode = "select" return album y += cons[0] if current_hovered_index == -1: current_hovered_index = 0 arrow_image = Image((cons[1], 0), (cons[2], size[1] + cons[0] * current_hovered_index + cons[3]), (cons[4], cons[4])) arrow_image.draw(screen) def album_deletion(): # Not finished yet pass def album_creation(): # Not finished yet pass # SELECT MODE class ImageRender: def __init__(self, album_name, initial_index=0): self.album_name = album_name self.index = initial_index self.image_load() def image_load(self): global current_img, current_album self.scaled_size = scaled_size image_list = storage["albums"][self.album_name] image_names = list(image_list.keys()) if not image_names: return self.current = image_names[self.index] selected_image = pygame.image.load(MAIN_PATH + IMAGES_PATH + self.album_name + "/" + self.current) self.scaled_image = pygame.transform.scale(selected_image, (self.scaled_size[2], self.scaled_size[3])) current_img = self.current current_album = self.album_name self.num_images = len(storage["albums"][self.album_name]) self.current_text = f"{self.index+1} of {self.num_images}" def render(self, screen): screen.blit(self.scaled_image, (self.scaled_size[0], self.scaled_size[1])) render_text(self.current_text, (area[0], area[1]), screen, oposite_color, True) pygame.display.flip() def update_index(self, increment): self.index += increment if self.index >= self.num_images: self.index = 0 elif self.index < 0: self.index = self.num_images - 1 self.image_load() @staticmethod def image_save(selected_album, current_img): for album in storage["albums"].values(): for image in album.values(): image[0] = 0 chosen = storage["albums"][selected_album][current_img] chosen[0] = 1 with open(DATA_FILE, 'w') as data_file: data_file.write(json.dumps(storage, indent=3)) image_rect = pygame.Rect(scaled_size) return image_rect def image_deletion(self): # Not finished yet pass def image_creation(self): # Not finished yet pass def reload_chosen(): for album in storage["albums"].values(): for image in album.values(): image[0] = 0 with open(DATA_FILE, 'w') as data_file: data_file.write(json.dumps(storage, indent=3)) # GAME MODE def timer(elapsed_time): hours = elapsed_time // 3600 minutes = (elapsed_time % 3600) // 60 seconds = elapsed_time % 60 timer_text = f"{hours:02d}:{minutes:02d}:{seconds:02d}" return timer_text # Puzzle loading and running logic def cut_image(image): piece_width = puzzle_size[0] // len(puzzle_matrix[0]) piece_height = puzzle_size[1] // len(puzzle_matrix) pieces = [] for row in range(len(puzzle_matrix)): for col in range(len(puzzle_matrix[0])): left, top = col * piece_width, row * piece_height piece_rect = pygame.Rect(left, top, piece_width, piece_height) piece_image = image.subsurface(piece_rect) piece_layer = pygame.Surface((piece_width, piece_height), pygame.SRCALPHA) if puzzle_version == GRID_CHOICES[0]: layer_path = pygame.image.load("images/assets/piece32.png").convert_alpha() else: layer_path = pygame.image.load("images/assets/piece45.png" if settings_data["size"] == SIZE_CHOICES[0] else "images/assets/piece60.png").convert_alpha() layer_path = pygame.transform.scale(layer_path, (piece_width, piece_height)) layer_path.set_colorkey((0, 0, 0)) piece_layer.blit(piece_image, (0, 0)) piece_layer.blit(layer_path, (0, 0)) pieces.append((piece_layer, (left, top))) return pieces def load_puzzle(): global puzzle_matrix, original_matrix, empty_position, puzzle_pieces, shuffled_pieces, puzzle_size, directions, cell_size, number_of_rows, original_image puzzle_version = settings_data["grid"] image_path = puzzle_data[f'current {puzzle_version}']['selected_image'] puzzle_image = pygame.image.load(MAIN_PATH + image_path) directions = {"up": [4, 8, 12], "down": [], "left": [13, 14, 15], "right": []} puzzle_matrix = np.array(puzzle_data[f'current {puzzle_version}']['matrix']) original_matrix = np.array(puzzle_data[f'puzzle {puzzle_version}']) empty_position = np.nonzero(puzzle_matrix == 0) number_of_rows = int(puzzle_version[0]) cell_size = puzzle_size[0] // number_of_rows original_image = pygame.transform.scale(puzzle_image, puzzle_size) puzzle_pieces = cut_image(original_image) if np.max(puzzle_matrix) == 0: shuffled_pieces = shuffle_pieces(puzzle_pieces) empty_position = (len(puzzle_matrix) - 1, len(puzzle_matrix[0]) - 1) load_puzzle() def shuffle_pieces(puzzle_pieces): global puzzle_matrix numbers = list(range(len(puzzle_pieces))) np.random.shuffle(numbers) puzzle_matrix = np.array(numbers).reshape(puzzle_matrix.shape) save_puzzle_state() update_directions() for i, (_, (left, top)) in enumerate(puzzle_pieces): row, col = divmod(numbers[i], len(puzzle_matrix[0])) puzzle_pieces[i] = (puzzle_pieces[i][0], (col * cell_size, row * cell_size)) return puzzle_pieces def move_pieces(clicked_row, clicked_col): global puzzle_matrix zero_positions = np.nonzero(puzzle_matrix == 0) zero_row, zero_col = zero_positions[0][0], zero_positions[1][0] if clicked_row == zero_row: if clicked_col < zero_col: direction = "right" else: direction = "left" elif clicked_col == zero_col: if clicked_row < zero_row: direction = "down" else: direction = "up" else: return if direction in ["up", "down"]: pieces_to_move = puzzle_matrix[min(clicked_row, zero_row):max(clicked_row, zero_row) + 1, clicked_col] else: pieces_to_move = puzzle_matrix[clicked_row, min(clicked_col, zero_col):max(clicked_col, zero_col) + 1] if direction == "right": puzzle_matrix[clicked_row, clicked_col:zero_col + 1] = np.roll(puzzle_matrix[clicked_row, clicked_col:zero_col + 1], 1) elif direction == "left": puzzle_matrix[clicked_row, zero_col:clicked_col + 1] = np.roll(puzzle_matrix[clicked_row, zero_col:clicked_col + 1], -1) elif direction == "down": puzzle_matrix[min(clicked_row, zero_row):max(clicked_row, zero_row) + 1, clicked_col] = np.roll(pieces_to_move, 1) elif direction == "up": puzzle_matrix[min(clicked_row, zero_row):max(clicked_row, zero_row) + 1, clicked_col] = np.roll(pieces_to_move, -1)[:pieces_to_move.shape[0]] save_puzzle_state() update_directions() return pieces_to_move.any() def update_directions(): global directions, puzzle_matrix zero_positions = np.nonzero(puzzle_matrix == 0) zero_row, zero_col = zero_positions[0][0], zero_positions[1][0] directions = { "up": puzzle_matrix[zero_row + 1:, zero_col].flatten().tolist() if zero_row < number_of_rows - 1 else [], "down": puzzle_matrix[:zero_row, zero_col].flatten().tolist() if zero_row > 0 else [], "left": puzzle_matrix[zero_row, zero_col + 1:].flatten().tolist() if zero_col < number_of_rows - 1 else [], "right": puzzle_matrix[zero_row, :zero_col].flatten().tolist() if zero_col > 0 else [], } update_directions() def save_puzzle_state(): puzzle_matrix_list = puzzle_matrix.tolist() puzzle_data[f'current {puzzle_version}']['matrix'] = puzzle_matrix_list with open(PUZZLE_FILE, 'w') as puzzle_file: json.dump(puzzle_data, puzzle_file) def update_puzzle_data(completed, sorted, current_time, current_moves, real_time): path_components = puzzle_data[f'current {puzzle_version}']['selected_image'].split('/')[2:] current_data = storage["albums"][path_components[0]] for component in path_components[1:]: if component in current_data: current_data = current_data[component] else: print("Something went wrong while updating puzzle data") return print(current_data) initial_chosen, initial_completed, initial_sorted, initial_moves, initial_time, _, _ = current_data initial_completed = int(completed) initial_sorted = int(sorted) initial_moves += current_moves initial_time_seconds = sum(int(x) * 60**i for i, x in enumerate(reversed(initial_time.split(":")))) total_time_seconds = initial_time_seconds + real_time hours, remainder = divmod(total_time_seconds, 3600) minutes, seconds = divmod(remainder, 60) formatted_time = f"{hours:02d}:{minutes:02d}:{seconds:02d}" current_data = [initial_chosen, initial_completed, initial_sorted, initial_moves, formatted_time, current_time, current_moves] print(current_data, path_components) storage["albums"][path_components[0]][path_components[1]] = current_data with open(DATA_FILE, 'w') as data_file: data_file.write(json.dumps(storage, indent=3)) # SETTINGS MODE def play_music(): music_file = settings_data["music"]["version"] volume = settings_data["music"]["volume"] pygame.mixer.music.load("misc/" + music_file + ".mp3") pygame.mixer.music.set_volume(volume) pygame.mixer.music.play(-1) if settings_data["music"]["on"] is True: play_music() def play_sound(): sound_file = settings_data["sound"]["version"] volume = settings_data["sound"]["volume"] sound = pygame.mixer.Sound("misc/" + sound_file + ".ogg") sound.set_volume(volume) pygame.mixer.Sound.play(sound) def set_hue(): global hue_value if settings_data["style"] == STYLE_CHOICES[0]: x = 0 elif settings_data["style"] == STYLE_CHOICES[2]: x = 1 gui = "gui color" hue_value = settings_data[gui][x] keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: hue_value = (hue_value - 1) % 360 if keys[pygame.K_RIGHT]: hue_value = (hue_value + 1) % 360 sprite_sheet.set_hue(hue_value) pygame.image.save(sprite_sheet.sheet, MAIN_PATH + SIZE_PATH + str(INT) + STYLE_PATH + "spritesheet.png") settings_data[gui][x] = hue_value with open(SETTINGS_FILE, "w") as settings_file: json.dump(settings_data, settings_file) def change_hue(image, hue): new_image = image.copy() for x in range(new_image.get_width()): for y in range(new_image.get_height()): color = new_image.get_at((x, y)) h, s, v, a = pygame.Color(color) h = (h + hue) % 360 new_color = pygame.Color(0, 0, 0, 0) new_color.hsva = (h, s, v, a) new_image.set_at((x, y), new_color) return new_image def export(): # Export data function, not finished yet pass def reset(): # Reset all data function, not finished yet pass def info(): # Not finished yet pass # Stats def stats(): # Not finished yet pass # Buttons (maybe optimise?) if settings_data["size"] == SIZE_CHOICES[0]: px12 = (12, 12) px14 = (14, 14) timer_button = Button((0, 0), (7, 3), px12) menu_button = Button((15, 0), (80, 3), px12) hint_button = Button((30, 0), (95, 3), px12) shuffle_button = Button((45, 0), (110, 3), px12) close_button = Button((60, 0), (125, 3), px12) finished_button = Button((75, 0), (7, 3), px12) not_finished_button = Button((90, 0), (7, 3), px12) delete_button = Button((105, 0), (95, 3), px12) continue_button = Button((120, 0), (110, 3), px12) settings_button = Button((135, 0), (7, 3), px12) quick_add_button = Button((34, 30), (22, 3), (55, 12)) new_button = Button((150, 0), (80, 3), px12) info_button = Button((165, 0), (7, 3), px12) stats_button = Button((180, 0), (95, 3), px12) to_left_button = Button((0, 30), (33, 136), px14) to_right_button = Button((17, 30), (97, 136), px14) BACKGROUND_IMAGE = Image((0, 64), (0, 0), (WIDTH, HEIGHT)) ver1_layer = sprite_sheet.get_sprite((147, 64), WIDTH, HEIGHT, transparent) ver2_layer = sprite_sheet.get_sprite((294, 64), WIDTH, HEIGHT, transparent) elif settings_data["size"] == SIZE_CHOICES[1]: px16 = (16, 16) px20 = (20, 20) timer_button = Button((0, 0), (8, 4), px16) menu_button = Button((19, 0), (118, 4), px16) hint_button = Button((38, 0), (138, 4), px16) shuffle_button = Button((57, 0), (158, 4), px16) close_button = Button((76, 0), (178, 4), px16) finished_button = Button((95, 0), (8, 4), px16) not_finished_button = Button((114, 0), (8, 4), px16) delete_button = Button((133, 0), (138, 4), px16) continue_button = Button((152, 0), (158, 4), px16) settings_button = Button((171, 0), (8, 4), px16) quick_add_button = Button((46, 38), (28, 4), (86, 16)) new_button = Button((190, 0), (118, 4), px16) info_button = Button((209, 0), (8, 4), px16) stats_button = Button((228, 0), (138, 4), px16) to_left_button = Button((0, 38), (46, 191), px20) to_right_button = Button((23, 38), (136, 191), px20) BACKGROUND_IMAGE = Image((0, 84), (0, 0), (WIDTH, HEIGHT)) ver1_layer = sprite_sheet.get_sprite((205, 84), WIDTH, HEIGHT, transparent) ver2_layer = sprite_sheet.get_sprite((410, 84), WIDTH, HEIGHT, transparent) else: px32 = (32, 32) px40 = (40, 40) timer_button = Button((0, 0), (12, 6), px32) menu_button = Button((35, 0), (241, 6), px32) hint_button = Button((72, 0), (278, 6), px32) shuffle_button = Button((105, 0), (315, 6), px32) close_button = Button((140, 0), (352, 6), px32) finished_button = Button((175, 0), (12, 6), px32) not_finished_button = Button((210, 0), (8, 6), px32) delete_button = Button((245, 0), (278, 6), px32) continue_button = Button((280, 0), (315, 6), px32) settings_button = Button((315, 0), (12, 6), px32) quick_add_button = Button((86, 70), (49, 6), (187, 32)) new_button = Button((350, 0), (241, 6), px32) info_button = Button((385, 0), (12, 6), px32) stats_button = Button((420, 0), (278, 6), px32) to_left_button = Button((0, 70), (107, 372), px40) to_right_button = Button((43, 70), (248, 372), px40) BACKGROUND_IMAGE = Image((0, 156), (0, 0), (WIDTH, HEIGHT)) ver1_layer = sprite_sheet.get_sprite((399, 156), WIDTH, HEIGHT, transparent) ver2_layer = sprite_sheet.get_sprite((798, 156), WIDTH, HEIGHT, transparent) settings_x_position = 50 settings_y_offset = 10 slider_size = (80, 12) button_size = (10, 16) space_between_settings = 20 gui_color_slider = Slider(slider_size, button_size, (settings_x_position, 10), (0, 360)) sensitivity_slider = Slider(slider_size, button_size, (settings_x_position, 58), (0, 100)) music_volume_slider = Slider(slider_size, button_size, (settings_x_position, 106), (0, 1)) sound_volume_slider = Slider(slider_size, button_size, (settings_x_position, 154), (0, 1)) def load_data(): path_components = puzzle_data[f'current {puzzle_version}']['selected_image'].split('/')[2:] current_data = storage["albums"][path_components[0]] for component in path_components[1:]: current_data = current_data[component] completed = current_data[1] not_shuffled = current_data[2] timer_text = current_data[5] current_timer_text = timer_text displayed_current_moves = current_data[6] current_moves = 0 print("loaded from -", current_data, path_components) return completed, not_shuffled, timer_text, current_timer_text, displayed_current_moves, current_moves completed, not_shuffled, timer_text, current_timer_text, displayed_current_moves, current_moves = load_data() if completed: state_button = finished_button else: state_button = not_finished_button game_buttons = [timer_button, menu_button, hint_button, shuffle_button, close_button] menu_buttons = [settings_button, quick_add_button, new_button, delete_button, continue_button, close_button] select_buttons = [state_button, menu_button, delete_button, continue_button, close_button, to_left_button, to_right_button] settings_buttons = [info_button, menu_button, stats_button, continue_button, close_button] do_not_save = False def button_check(pressed_button, run, current_mode, timer_running): global completed, not_shuffled, current_timer_text, current_moves, real_time do_not_save = False if pressed_button == close_button: run = False save_puzzle_state() update_puzzle_data(completed, not_shuffled, current_timer_text, current_moves, real_time) reload_chosen() elif pressed_button == settings_button: current_mode = "settings" elif pressed_button == menu_button: current_mode = "menu" timer_running = False elif pressed_button == continue_button: current_mode = "game" elif pressed_button == delete_button: if current_mode == "menu": album_deletion() else: # Not finished yet pass elif pressed_button == new_button: album_creation() elif pressed_button == quick_add_button: quick_game() current_mode = "game" not_shuffled = 1 current_timer_text = "00:00:00" current_moves = 0 do_not_save = True elif pressed_button in [not_finished_button, finished_button]: # Not finished yet pass elif pressed_button == info_button: info() elif pressed_button == stats_button: stats() return run, current_mode, timer_running, do_not_save win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) | win32con.WS_EX_LAYERED) win32gui.SetLayeredWindowAttributes(hwnd, win32api.RGB(*transparent), 0, win32con.LWA_COLORKEY) set_shadow_style(hwnd) def data_check(current_mode): if current_mode == "select": background_image = ver2_layer current_buttons = select_buttons elif current_mode == "game": current_buttons = game_buttons background_image = ver1_layer elif current_mode == "menu": current_buttons = menu_buttons background_image = ver1_layer elif current_mode == "settings" or current_mode == "stats" or current_mode == "info": current_buttons = settings_buttons background_image = ver1_layer return background_image, current_buttons scroll_offset = 0 space_between_settings = 5 settings_x_position = puzzle_size[0] settings_y_offset = 5 total_settings_height = len(settings_data) * (cons[0] + space_between_settings) max_scroll_offset = max(0, total_settings_height - non_movable_area.height) # Game state run = True current_mode = "menu" # Dragging and mouse interaction dragging = False can_move = False start_pos = (0, 0) pressed_button = None # Select mode clicked_image = False image_display = None clicked = None # Puzzle-related stuff last_shuffle_time = 0 user_win = False moved = None # Timer-related stuff timer_running = False timer_start_time = 0 elapsed_time = 0 real_time = elapsed_time total_paused_time = 0 timer_button.disable() while run: background_image, current_buttons = data_check(current_mode) current_time = pygame.time.get_ticks() screen.fill((255,0,128)) screen.blit(BACKGROUND_IMAGE.img, (0, 0)) screen.blit(background_image, (0, 0)) mouse_pos = pygame.mouse.get_pos() for button in current_buttons: button.update(mouse_pos) for button in current_buttons: button.draw(screen) clock.tick(FPS) if current_mode == "menu": selected_album = render_albums() if current_mode == "select": if image_display is None or current_album != selected_album: selected_image = ImageRender(selected_album) image_display = True selected_image.render(screen) for event in pygame.event.get(): if event.type == pygame.QUIT or event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: save_puzzle_state() update_puzzle_data(completed, not_shuffled, current_timer_text, current_moves, real_time) reload_chosen() run = False elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: if not non_movable_area.collidepoint(pygame.mouse.get_pos()): can_move = True if current_mode == "select": clicked_image_rect = ImageRender.image_save(selected_album, current_img) clicked_image = clicked_image_rect.collidepoint(event.pos) if clicked_image: clicked = True update_puzzle_data(completed, not_shuffled, current_timer_text, current_moves, real_time) puzzle_data[f'current {puzzle_version}']["selected_image"] = IMAGES_PATH + selected_album + "/" + current_img with open(PUZZLE_FILE, 'w') as puzzle_file: puzzle_file.write(json.dumps(puzzle_data)) completed, not_shuffled, timer_text, current_timer_text, displayed_current_moves, current_moves = load_data() elapsed_time = 0 current_moves = 0 shuffle_pieces(puzzle_pieces) load_puzzle() current_mode = "game" timer_running = False timer_start_time = 0 real_time = elapsed_time total_paused_time = 0 timer_button.disable() clicked = None last_click_time = pygame.time.get_ticks() if to_left_button.is_hovered(event.pos): selected_image.update_index(-1) last_image_switch_time = current_time if to_right_button.is_hovered(event.pos): selected_image.update_index(1) last_image_switch_time = current_time elif current_mode == "game": if non_movable_area.collidepoint(event.pos) and not timer_running: timer_running = True timer_button.enable() timer_start_time = pygame.time.get_ticks() elif timer_button.rect.collidepoint(event.pos) and timer_running: timer_running = False timer_button.disable() total_paused_time += pygame.time.get_ticks() - timer_start_time dragging = True start_pos = pygame.mouse.get_pos() for button in current_buttons: if button.rect.collidepoint(start_pos): pressed_button = button run, current_mode, timer_running, do_not_save = button_check(pressed_button, run, current_mode, timer_running) elif event.type == pygame.MOUSEMOTION and can_move: new_pos = pygame.mouse.get_pos() offset_x = new_pos[0] - start_pos[0] offset_y = new_pos[1] - start_pos[1] window_coords[0] += offset_x window_coords[1] += offset_y move_win(window_coords) elif event.type == pygame.MOUSEBUTTONUP: dragging = False pressed_button = None can_move = False elif event.type == pygame.MOUSEBUTTONDOWN and current_mode == "settings": if event.button == 4: scroll_offset -= 20 scroll_offset = max(0, scroll_offset) elif event.button == 5: scroll_offset += 20 scroll_offset = min(scroll_offset, max_scroll_offset) if current_mode == "game": if timer_running: current_time = pygame.time.get_ticks() - timer_start_time elapsed_time = (current_time + total_paused_time) // 1000 real_time = elapsed_time hours, minutes, seconds = map(int, timer_text.split(':')) total_seconds = hours * 3600 + minutes * 60 + seconds elapsed_time += total_seconds current_timer_text = timer(elapsed_time) render_text(current_timer_text, (timer_area.x, timer_area.y), screen, oposite_color) # Puzzle if pressed_button == shuffle_button and current_time - last_shuffle_time > 500: if not_shuffled: np.copyto(puzzle_matrix, original_matrix) user_win = False not_shuffled = 0 else: shuffle_pieces(puzzle_pieces) not_shuffled = 1 save_puzzle_state() update_directions() last_shuffle_time = current_time for row in range(len(puzzle_matrix)): for col in range(len(puzzle_matrix[0])): if puzzle_matrix[row][col]: piece_x = col * puzzle_size[0] // len(puzzle_matrix[0]) + non_movable_area.x piece_y = row * puzzle_size[1] // len(puzzle_matrix) + non_movable_area.y screen.blit(puzzle_pieces[puzzle_matrix[row][col] - 1][0], (piece_x, piece_y)) if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: mouse_x, mouse_y = pygame.mouse.get_pos() clicked_row, clicked_col = (mouse_y - non_movable_area.y) // cell_size, (mouse_x - non_movable_area.x) // cell_size if 0 <= clicked_row < len(puzzle_matrix) and 0 <= clicked_col < len(puzzle_matrix[0]): if puzzle_matrix[clicked_row, clicked_col] and clicked is None: moved = move_pieces(clicked_row, clicked_col) if settings_data["sound"]["on"] is True and moved is not None: current_moves += 1 user_win = True print("clicked:", current_moves) play_sound() if pressed_button == hint_button: screen.blit(original_image, non_movable_area) if number_of_rows == 4 and puzzle_data[f'current {puzzle_version}']['matrix'] == puzzle_data[f'puzzle {puzzle_version}'] and user_win is True: completed = 1 not_shuffled = 1 user_win = False timer_running = False start_time = pygame.time.get_ticks() fade_in_end_time = start_time + 2 * 1000 fade_out_start_time = start_time + 4 * 1000 end_time = start_time + 6 * 1000 alpha = 0 text_alpha = 0 fade_in_speed = 100 / (fade_in_end_time - start_time) fade_out_speed = 100 / (fade_out_start_time - fade_in_end_time) while pygame.time.get_ticks() < end_time: surface = pygame.Surface((non_movable_area.width, non_movable_area.height), pygame.SRCALPHA) surface.fill((0, 0, 0, alpha)) print(alpha) screen.blit(surface, (non_movable_area.x, non_movable_area.y)) render_text("You win !!", (win_area.x, win_area.y), screen, (255, 255, 255, text_alpha), True) if pygame.time.get_ticks() < fade_in_end_time: alpha += fade_in_speed text_alpha += fade_in_speed elif fade_in_end_time <= pygame.time.get_ticks() < fade_out_start_time: alpha = max(0, alpha) text_alpha = max(0, text_alpha) elif pygame.time.get_ticks() >= fade_out_start_time: alpha -= fade_out_speed text_alpha -= fade_out_speed alpha = max(0, alpha) text_alpha = max(0, text_alpha) pygame.display.flip() pygame.time.delay(16) if current_mode == "settings": clipped_rect = pygame.Rect(non_movable_area.x, non_movable_area.y, settings_x_position, non_movable_area.height) screen.set_clip(clipped_rect) for index, setting_name in enumerate(settings_data): if setting_name != "keybinds": y = slider_size[1] - scroll_offset + index * (cons[0] + space_between_settings) value = settings_data[setting_name] render_settings_text(setting_name.upper(), value, (settings_x_position // 2 + 9, y - settings_y_offset), screen) if setting_name == "gui color": gui_color_slider.update_position((settings_x_position, y + 10)) gui_color_slider.blit(screen) elif setting_name == "sensitivity": sensitivity_slider.update_position((settings_x_position, y + 10)) sensitivity_slider.blit(screen) elif setting_name == "music": music_volume_slider.update_position((settings_x_position, y + 10)) music_volume_slider.blit(screen) elif setting_name == "sound": sound_volume_slider.update_position((settings_x_position, y + 10)) sound_volume_slider.blit(screen) screen.set_clip(None) pygame.display.flip() pygame.quit()