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 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) gui = "gui color" music = "music" sound = "sound" style = "style" current_size = settings_data["size"] current_style = settings_data["style"] # Configuration SIZE_CHOICES = ["small", "medium", "big"] STYLE_CHOICES = ["classic", "original", "dark"] GRID_CHOICES = ["4x4", "3x3"] MUSIC_CHOICES = ["funky leap", "serenity", "sunny day", "epec unf"] SOUND_CHOICES = ["puzzle", "wood", "metal pipe", "lego breaking"] DISPLAY_CHOICES = ["time", "moves", "none"] LANGUAGE_CHOICES = ["english", "russian", "latvian"] FONTS = ["msreferencesansserif", "arial", "bahnschrift"] if current_style == STYLE_CHOICES[0]: FONT = FONTS[0] STYLE_PATH = "1_" text_color = (1, 35, 61) oposite_color = white elif current_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 current_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) # --> puzzle_size[2] and puzzle_size[3] (128) - puzzle size (?x? px) non_movable_area = pygame.Rect(8, 19, 128, 128) # --> scaled_size[0] (8) - x position for image in game mode # --> scaled_size[1] (19) - y position for image # --> scaled_size[2] and scaled_size[3] (128) - image size (?x? px) timer_area = pygame.Rect(22, 1, 55, 12) # --> timer_area[0] (22) - position x for timer area in game mode # --> timer_area[1] (1) - position y for timer area # --> timer_area[2] (55) - width of timer area # --> timer_area[3] (12) - heigth of timer area win_area = pygame.Rect(74, 78, 128, 128) # --> win_area[0] (74) - position x for "You win !!" text area in game mode # --> win_area[1] (78) - position y for "You win !!" text area # --> win_area[2] (128) - width of "You win !!" text area # --> win_area[3] (128) - heigth of "You win !!" text area size = [20, 25, 116, 15] cons = [15, 195, 12, 3, 9] # size and cons (constants) are too complicated, I cant remember what each element was, sorry :( scaled_size = (18, 21, 108, 108) # --> scaled_size[0] (18) - x position for image in select mode # --> scaled_size[1] (21) - y position for image # --> scaled_size[2] and scaled_size[3] (108) - image size (?x? px) area = (72, 142) # --> area[0] (72) - x position for text "? of ?" in select mode # --> area[1] (142) - y position for text "? of ?" spaces = [14, 18, 20, 30, 8] # --> spaces[0] (14) - space between setting name and setting in settings mode, increasing # --> spaces[1] (18) - space between non_movable_area edge, decreasing # --> spaces[2] (20) - space between setting names, increasing # --> spaces[3] (30) - space between slider and non_movable_area edge, decreasing # --> spaces[4] (8) - space between setting names and non_movable_area edge, increasing f_line = [92, 30, 80, 12] s_line = [92, 45, 80, 12] s_button = [175, 30, 10, 16] # for list in f_line, s_line and s_button: # --> list[0] (135, 135, 218) - position x for element in spritesheet # --> list[1] (38, 53, 38) - position y for element in spritesheet # --> list[2] (80, 80, 10) - width of element in settings mode # --> list[3] (12, 12, 16) - heigth of element arrow1 = (261, 0, 10, 13) arrow2 = (274, 0, 10, 13) spaces2 = [4, 5, 2, 5, 60, 42] spaces3 = [10, 20, 23] # --> i forgor U+1F480 elif current_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) spaces = [14, 30, 30, 40, 8] f_line = [135, 38, 100, 16] s_line = [135, 58, 100, 16] s_button = [238, 38, 12, 20] arrow1 = (261, 0, 10, 13) arrow2 = (274, 0, 10, 13) spaces2 = [4, 5, 2, 5, 60, 42] spaces3 = [10, 20, 23] if current_style == STYLE_CHOICES[2]: spaces2[1] = 2 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 = 26 FONT_SIZE2 = 22 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, 58, 328, 35] cons = [35, 455, 26, 10, 26] scaled_size = (43, 48, 310, 310) area = (197, 390) spaces = [24, 34, 50, 84, 8] f_line = [276, 70, 200, 32] s_line = [276, 105, 200, 32] s_button = [479, 70, 24, 40] arrow1 = (485, 0, 21, 32) arrow2 = (509, 0, 21, 32) spaces2 = (4, 0, 2, 5, 60, 80) spaces3 = [50, 40, 43] if current_style == STYLE_CHOICES[2]: cons[3] = 8 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, popup_text=None): self.sprite_sheet = sprite_sheet self.sprite_position = sprite_position self.window_position = window_position self.size = size self.popup_text = popup_text self.hovered = False self.disabled = False self.popup_timer = 0 self.popup_duration = 3000 self.popup_surface = None 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) if self.popup_text is not None and self.hovered: self.popup_timer += FPS if self.popup_timer >= self.popup_duration: self.show_popup(mouse_pos) else: self.popup_timer = 0 self.popup_surface = None def show_popup(self, mouse_pos): font = pygame.font.Font(None, 22) text_surface = font.render(self.popup_text, True, (0, 0, 0), (255, 255, 225)) text_rect = text_surface.get_rect(center=mouse_pos) popup_width = text_rect.width + 4 popup_height = text_rect.height + 4 popup_x = min(mouse_pos[0], WIDTH - popup_width - 1) popup_y = min(mouse_pos[1] + 20, HEIGHT - popup_height - 1) self.popup_surface = pygame.Surface((popup_width, popup_height), pygame.SRCALPHA) pygame.draw.rect(self.popup_surface, (0, 0, 0), self.popup_surface.get_rect()) self.popup_surface.blit(text_surface, (2, 2)) self.popup_rect = self.popup_surface.get_rect(topleft=(popup_x, popup_y)) def draw(self, screen): if not self.disabled: screen.blit(self.hover_image if self.hovered else self.img, self.rect) if self.popup_surface: screen.blit(self.popup_surface, self.popup_rect) else: screen.blit(self.img, self.rect) # 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, position, screen, value=None,color=text_color): font = pygame.font.SysFont(FONT, FONT_SIZE2-1, italic=True) if value is not None: text_render = font.render(f"{text}: {value}", True, color) else: text_render = font.render(text, True, color) text_rect = text_render.get_rect(center=position) screen.blit(text_render, text_rect.topleft) # 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[current_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(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 current_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(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 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] 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 scroll_offset = 0 settings_x_position = puzzle_size[0] total_settings_height = len(settings_data) * (cons[0] + spaces[1]) max_scroll_offset = max(0, total_settings_height - non_movable_area.height) active_sliders = {gui: False, music: False, sound: False} gap = (s_button[2]/2)-(s_line[3]/2)-1 def slider(position, value_range, value, active_slider, setting): second_x = value / value_range * s_line[2] value_pos = second_x - s_button[2] / 2 first_line = Image((f_line[0], f_line[1]), (position[0], position[1]), (f_line[2], f_line[3])) second_line = Image((s_line[0], s_line[1]), (position[0], position[1]), (second_x, s_line[3])) slider_button = Button((s_button[0], s_button[1]), (position[0] + value_pos, position[1] + gap), (s_button[2], s_button[3])) if active_slider[setting] and pygame.mouse.get_pressed()[0]: mouse_pos = pygame.mouse.get_pos() if slider_button.rect.inflate(100, 25).collidepoint(mouse_pos): mouse_x = mouse_pos[0] min_x = position[0] - s_button[2] / 2 max_x = position[0] + s_line[2] - s_button[2] / 2 new_value_pos = min(max(mouse_x - s_button[2] / 2, min_x), max_x) new_value = int(((new_value_pos - min_x) / (max_x - min_x)) * value_range) second_x = new_value_pos - position[0] + s_button[2] / 2 slider_button.rect.x = new_value_pos value = new_value data_set(setting, value) return first_line, second_line, slider_button active_selects = {"size": False, "style": False, "grid": False, "music_version": False, "sound_version": False} def arrows(position, setting, value, choices, event_list): index = choices.index(value) arrow_width = arrow1[2] font = pygame.font.SysFont(FONT, FONT_SIZE) text_render = font.render(value, True, text_color) text_rect = text_render.get_rect() text_x = position[0] + (arrow_width + spaces2[0]) / 2 - text_rect.width / 2 + spaces2[5] text_y = position[1] - spaces2[1] value_text = screen.blit(text_render, (text_x, text_y)) first_arrow = Button((arrow1[0], arrow1[1]), (text_x - arrow_width - spaces2[0], position[1]), (arrow_width, arrow1[3])) second_arrow_x = text_x + text_rect.width + spaces2[2] second_arrow = Button((arrow2[0], arrow2[1]), (second_arrow_x , position[1]), (arrow2[2], arrow2[3])) for event in event_list: if event.type == pygame.MOUSEBUTTONDOWN: if first_arrow.rect.collidepoint(event.pos): index -= 1 if index < 0: index = len(choices) - 1 value = choices[index] data_set(setting, value) elif second_arrow.rect.collidepoint(event.pos): index += 1 if index >= len(choices): index = 0 value = choices[index] data_set(setting, value) return first_arrow, value_text, second_arrow def data_set(setting, value): if setting == "size": settings_data["size"] = value elif setting == "style": settings_data["style"] = value elif setting == "grid": settings_data["grid"] = value elif setting == gui: settings_data[gui] = value elif setting == music: if isinstance(value, str): settings_data[sound]["version"] = value else: settings_data[music]["volume"] = value update_music_volume() elif setting == sound: if isinstance(value, str): settings_data[sound]["version"] = value else: settings_data[sound]["volume"] = value update_sound_volume() with open(SETTINGS_FILE, "w") as settings_file: json.dump(settings_data, settings_file, indent=2) def play_music(): music_file = settings_data[music]["version"] music_file = music_file.replace(" ", "_") volume = settings_data[music]["volume"] pygame.mixer.music.load("misc/" + music_file + ".mp3") pygame.mixer.music.set_volume(volume / 100) pygame.mixer.music.play(-1) def play_sound(): sound_file = settings_data[sound]["version"] sound_file = sound_file.replace(" ", "_") volume = settings_data[sound]["volume"] current_sound = pygame.mixer.Sound("misc/" + sound_file + ".ogg") current_sound.set_volume(volume / 100) return current_sound current_sound = play_sound() def update_music_volume(): volume = settings_data[music]["volume"] pygame.mixer.music.set_volume(volume / 100) def update_sound_volume(): volume = settings_data[sound]["volume"] current_sound.set_volume(volume / 100) # Not configured properly def set_hue(): global hue_value if settings_data["style"] == STYLE_CHOICES[0]: x = 0 elif settings_data["style"] == STYLE_CHOICES[2]: x = 1 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, 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, indent=2) 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 # STATS MODE #INFO MODE text_size = [3, 5] def render_text_in_rect(long_text, rect_area): font = pygame.font.SysFont(FONT, FONT_SIZE-text_size[0]) lines = [] words = long_text.split() current_line = "" for word in words: test_line = current_line + word + " " if font.size(test_line)[0] <= rect_area.width: current_line = test_line else: lines.append(current_line.strip()) current_line = word + " " lines.append(current_line.strip()) line_height = font.get_linesize() text_surfaces = [] text_rects = [] for i, line in enumerate(lines): text_render = font.render(line, True, pygame.Color(*text_color)) text_rect = text_render.get_rect(topleft=(rect_area.x, rect_area.y + i * line_height)) text_surfaces.append(text_render) text_rects.append(text_rect) return text_surfaces, text_rects # Example usage rect_area = non_movable_area.copy() rect_area.x += text_size[1] rect_area.width -= text_size[1] long_text = ( "Picture Puzzle, once a feature in Windows Vista and 7's 'Desktop Gadgets,' " "faced an end in 2012 due to security concerns. This game is a remake of original " "Picture Puzzle with modified and upgraded functionality. Music autor is SmugBurger, " "sounds taken from open sources." ) text_surfaces, text_rects = render_text_in_rect(long_text, rect_area) # Buttons (maybe optimise?) if current_size == SIZE_CHOICES[0]: px12 = (12, 12) px14 = (14, 14) timer_button = Button((0, 0), (7, 3), px12, "Pause timer") menu_button = Button((15, 0), (80, 3), px12, "Menu") hint_button = Button((30, 0), (95, 3), px12, "Hint") shuffle_button = Button((45, 0), (110, 3), px12, "Shuffle") close_button = Button((60, 0), (125, 3), px12, "Close") 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, "Delete") continue_button = Button((120, 0), (110, 3), px12, "Continue") settings_button = Button((135, 0), (7, 3), px12, "Settings") quick_add_button = Button((34, 30), (22, 3), (55, 12)) new_button = Button((150, 0), (80, 3), px12, "New") info_button = Button((165, 0), (7, 3), px12, "Info") stats_button = Button((180, 0), (95, 3), px12, "Stats") 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 current_size == SIZE_CHOICES[1]: px16 = (16, 16) px20 = (20, 20) timer_button = Button((0, 0), (8, 4), px16, "Pause timer") menu_button = Button((19, 0), (118, 4), px16, "Menu") hint_button = Button((38, 0), (138, 4), px16, "Hint") shuffle_button = Button((57, 0), (158, 4), px16, "Shuffle") close_button = Button((76, 0), (178, 4), px16, "Close") 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, "Delete") continue_button = Button((152, 0), (158, 4), px16, "Continue") settings_button = Button((171, 0), (8, 4), px16, "Settings") quick_add_button = Button((46, 38), (28, 4), (86, 16)) new_button = Button((190, 0), (118, 4), px16, "Stats") info_button = Button((209, 0), (8, 4), px16, "Info") stats_button = Button((228, 0), (138, 4), px16, "Stats") 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, "Pause timer") menu_button = Button((35, 0), (241, 6), px32, "Menu") hint_button = Button((72, 0), (278, 6), px32, "Hint") shuffle_button = Button((105, 0), (315, 6), px32, "Shuffle") close_button = Button((140, 0), (352, 6), px32, "Close") 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, "Delete") continue_button = Button((280, 0), (315, 6), px32, "Continue") settings_button = Button((315, 0), (12, 6), px32, "Settings") quick_add_button = Button((86, 70), (49, 6), (187, 32)) new_button = Button((350, 0), (241, 6), px32, "Stats") info_button = Button((385, 0), (12, 6), px32, "Info") stats_button = Button((420, 0), (278, 6), px32, "Stats") 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) 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 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: current_mode = "info" elif pressed_button == stats_button: current_mode = "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 # Game state run = True current_mode = "menu" # Dragging and mouse interaction dragging = False can_move = False start_pos = (0, 0) pressed_button = None slider_moving = False setting = 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() if settings_data[music]["on"] is True: play_music() 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) event_list = pygame.event.get() for event in event_list: 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 slider_moving = False pressed_button = None can_move = False setting = None 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"] and moved is not None: current_moves += 1 user_win = True pygame.mixer.Sound.play(current_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 = f_line[3] - scroll_offset + index * (cons[0] + spaces[2]) + spaces[1] value = settings_data[setting_name] if setting_name in [gui, music, sound]: if setting_name in [music, sound]: value = settings_data[setting_name]["volume"] value2 = settings_data[setting_name]["version"] if setting_name == "size": render_settings_text(setting_name.upper(),(settings_x_position // 2 + spaces[4], y), screen) size_choosing = arrows((settings_x_position // 2 - spaces[3], y + spaces[0]), setting_name, value, SIZE_CHOICES, event_list) size_choosing[0].update(mouse_pos) size_choosing[2].update(mouse_pos) size_choosing[0].draw(screen) size_choosing[2].draw(screen) active_selects["size"] = True elif setting_name == "style": render_settings_text(setting_name.upper(),(settings_x_position // 2 + spaces[4], y), screen) style_choosing = arrows((settings_x_position // 2 - spaces[3], y + spaces[0]), setting_name, value, STYLE_CHOICES, event_list) style_choosing[0].update(mouse_pos) style_choosing[2].update(mouse_pos) style_choosing[0].draw(screen) style_choosing[2].draw(screen) active_selects["style"] = True elif setting_name == "grid": render_settings_text(setting_name.upper(),(settings_x_position // 2 + spaces[4], y), screen) grid_choosing = arrows((settings_x_position // 2 - spaces[3], y + spaces[0]), setting_name, value, GRID_CHOICES, event_list) grid_choosing[0].update(mouse_pos) grid_choosing[2].update(mouse_pos) grid_choosing[0].draw(screen) grid_choosing[2].draw(screen) active_selects["grid"] = True elif setting_name == gui: render_settings_text(setting_name.upper(),(settings_x_position // 2 + spaces[4], y), screen, value) gui_color_slider = slider((settings_x_position // 2 - spaces[3], y + spaces[0]), 360, value, active_sliders, setting_name) for slider_part in gui_color_slider: slider_part.draw(screen) if dragging and gui_color_slider[2].rect.collidepoint(mouse_pos): active_sliders[gui] = True setting = setting_name elif not dragging: active_sliders[gui] = False elif setting_name == music: render_settings_text(setting_name.upper(),(settings_x_position // 2 + spaces[4], y), screen, value) music_volume_slider = slider((settings_x_position // 2 - spaces[3], y + spaces[0]), 100, value, active_sliders, setting_name) for slider_part in music_volume_slider: slider_part.draw(screen) if dragging and music_volume_slider[2].rect.collidepoint(mouse_pos): active_sliders[music] = True setting = setting_name elif not dragging: active_sliders[music] = False music_choosing = arrows((settings_x_position // 2 - spaces[3], y + spaces[0]+spaces3[2]), setting_name, value2, MUSIC_CHOICES, event_list) music_choosing[0].update(mouse_pos) music_choosing[2].update(mouse_pos) music_choosing[0].draw(screen) music_choosing[2].draw(screen) active_selects["music_version"] = True elif setting_name == sound: render_settings_text(setting_name.upper(),(settings_x_position // 2 + spaces[4], y+spaces3[1]), screen, value) y += spaces2[5] sound_volume_slider = slider((settings_x_position // 2 - spaces[3], y-spaces3[0]), 100, value, active_sliders, setting_name) for slider_part in sound_volume_slider: slider_part.draw(screen) if dragging and sound_volume_slider[2].rect.collidepoint(mouse_pos): active_sliders[sound] = True setting = setting_name elif not dragging: active_sliders[sound] = False sound_choosing = arrows((settings_x_position // 2 - spaces[3], y + spaces[0]), setting_name, value2, SOUND_CHOICES, event_list) sound_choosing[0].update(mouse_pos) sound_choosing[2].update(mouse_pos) sound_choosing[0].draw(screen) sound_choosing[2].draw(screen) active_selects["sound_version"] = True screen.set_clip(None) if current_mode == "info": for text_surface, text_rect in zip(text_surfaces, text_rects): screen.blit(text_surface, text_rect) if current_mode == "stats": print("Currently under maintenance") pygame.display.flip() pygame.quit()