diff --git a/TODO b/TODO index 5387531..5e53afa 100644 --- a/TODO +++ b/TODO @@ -3,14 +3,14 @@ TODO main: [x] fix image loading from last album [x] fix image choosing [x] add proper image loading from storage -[ ] add setting - move pieses = slow/normal/fast/instant -[ ] fix quick game window moving thing -[ ] add popup when loading time - load previous or reset +[meh] add setting - move pieses = slow/normal/fast/instant +[meh] fix quick game window moving thing +[meh] add popup when loading time - load previous or reset [ ] add small notes on button hover [ ] add functionality in select and menu mode -[ ] add settings, info and stats with full functionality -[ ] optimise code as possible -[ ] add splash screen +[sorta] add settings, info and stats with full functionality +[meh] optimise code as possible +[no] add splash screen [ ] add keybinds TODO additional: diff --git a/images/assets/01_sprites.png b/images/assets/01_sprites.png index 6f3fe36..6a526c7 100644 Binary files a/images/assets/01_sprites.png and b/images/assets/01_sprites.png differ diff --git a/images/assets/11_sprites.png b/images/assets/11_sprites.png index 7583652..6bcb108 100644 Binary files a/images/assets/11_sprites.png and b/images/assets/11_sprites.png differ diff --git a/main.py b/main.py index d03004a..cb87700 100644 --- a/main.py +++ b/main.py @@ -51,9 +51,9 @@ current_style = settings_data["style"] 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"] +MUSIC_CHOICES = ["funky leap", "serenity", "sunny day"] +SOUND_CHOICES = ["puzzle", "wood", "metal pipe", "lego breaking"] +DISPLAY_CHOICES = ["time", "moves", "none"] LANGUAGE_CHOICES = ["english", "russian", "latvian"] FONTS = ["msreferencesansserif", "arial", "bahnschrift"] @@ -150,6 +150,11 @@ elif current_size == SIZE_CHOICES[1]: 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] + if current_style == STYLE_CHOICES[2]: + spaces2[1] = 2 else: WIDTH, HEIGHT = 396, 425 @@ -168,15 +173,18 @@ else: 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, 8, 26] + 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] - if current_style == STYLE_CHOICES[0]: - cons[3] = 10 + arrow1 = (261, 0, 10, 13) + arrow2 = (274, 0, 10, 13) + spaces2 = (4, 5, 2, 5, 60, 42) + if current_style == STYLE_CHOICES[2]: + cons[3] = 8 puzzle_version = settings_data["grid"] @@ -318,7 +326,7 @@ def render_text(text, position, screen, color=text_color, centered=False): 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) + 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: @@ -643,14 +651,10 @@ def slider(position, value_range, value, active_slider, setting): 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 = Image((s_button[0], s_button[1]), (position[0] + value_pos, position[1] + gap), (s_button[2], s_button[3])) - mouse_buttons = pygame.mouse.get_pressed() - if active_slider[setting] and mouse_buttons[0]: - + 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 mouse_buttons[0] and slider_button.rect.inflate(100, 25).collidepoint(mouse_pos): - 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 @@ -663,15 +667,63 @@ def slider(position, value_range, value, active_slider, setting): 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 == gui: + 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: - settings_data[music]["volume"] = value - update_music_volume() + if isinstance(value, str): + settings_data[sound]["version"] = value + else: + settings_data[music]["volume"] = value + update_music_volume() elif setting == sound: - settings_data[sound]["volume"] = value - update_sound_volume() + 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) @@ -750,16 +802,49 @@ def reset(): # Reset all data function, not finished yet pass - -def info(): - # Not finished yet - pass +# STATS MODE -# Stats -def stats(): - # Not finished yet - pass +#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 +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?) @@ -896,9 +981,9 @@ def button_check(pressed_button, run, current_mode, timer_running): # Not finished yet pass elif pressed_button == info_button: - info() + current_mode = "info" elif pressed_button == stats_button: - stats() + current_mode = "stats" return run, current_mode, timer_running, do_not_save @@ -935,11 +1020,12 @@ run = True current_mode = "menu" # Dragging and mouse interaction dragging = False -slider_moving = False -setting = None can_move = False start_pos = (0, 0) pressed_button = None +slider_moving = False +setting = None +arrow = None # Select mode clicked_image = False image_display = None @@ -987,7 +1073,9 @@ while run: selected_image.render(screen) - for event in pygame.event.get(): + 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) @@ -1058,7 +1146,7 @@ while run: pressed_button = None can_move = False setting = None - + elif event.type == pygame.MOUSEBUTTONDOWN and current_mode == "settings": if event.button == 4: scroll_offset -= 20 @@ -1107,7 +1195,7 @@ while run: 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: + if settings_data[sound]["on"] and moved is not None: current_moves += 1 user_win = True print("clicked:", current_moves) @@ -1160,18 +1248,39 @@ while run: 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] + 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 = value["volume"] - render_settings_text(setting_name.upper(), - (settings_x_position // 2 + spaces[4], y), screen, value) - else: - render_settings_text(setting_name.upper(), - (settings_x_position // 2 + spaces[4], y), screen) + value = settings_data[setting_name]["volume"] + value2 = settings_data[setting_name]["version"] - if setting_name == gui: + 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) @@ -1181,6 +1290,7 @@ while run: 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) @@ -1189,8 +1299,16 @@ while run: setting = setting_name elif not dragging: active_sliders[music] = False + music_choosing = arrows((settings_x_position // 2 - spaces[3], y + spaces[0]+24), 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: - sound_volume_slider = slider((settings_x_position // 2 - spaces[3], y + spaces[0]), 100, value, active_sliders, setting_name) + render_settings_text(setting_name.upper(),(settings_x_position // 2 + spaces[4], y+20), screen, value) + y += spaces2[5] + sound_volume_slider = slider((settings_x_position // 2 - spaces[3], y-10), 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): @@ -1198,8 +1316,18 @@ while run: 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) pygame.display.flip() diff --git a/main_data.json b/main_data.json index ebfb6b2..3e4e99c 100644 --- a/main_data.json +++ b/main_data.json @@ -5,19 +5,19 @@ 0, 0, 0, - 93, - "00:01:05", - "00:00:40", - 0 + 99, + "00:01:11", + "00:00:46", + 6 ], "2.png": [ 0, 0, 0, - 57, - "00:00:23", - "00:00:23", - 1 + 85, + "00:00:40", + "00:00:40", + 0 ], "3.png": [ 0, diff --git a/main_puzzle.json b/main_puzzle.json index 9ed675e..212d5dc 100644 --- a/main_puzzle.json +++ b/main_puzzle.json @@ -1 +1 @@ -{"puzzle 4x4": [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 0]], "puzzle 3x3": [[1, 2, 3], [4, 5, 6], [7, 8, 0]], "current 4x4": {"matrix": [[13, 2, 9, 12], [4, 1, 6, 8], [5, 3, 10, 15], [14, 7, 11, 0]], "selected_image": "images/albums/Original images/1.png"}, "current 3x3": {"matrix": [[3, 6, 7], [5, 4, 1], [0, 8, 2]], "selected_image": "/images/albums/Birds/1.png"}} \ No newline at end of file +{"puzzle 4x4": [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 0]], "puzzle 3x3": [[1, 2, 3], [4, 5, 6], [7, 8, 0]], "current 4x4": {"matrix": [[10, 2, 5, 12], [13, 4, 3, 15], [6, 14, 8, 0], [11, 1, 7, 9]], "selected_image": "images/albums/Original images/2.png"}, "current 3x3": {"matrix": [[3, 6, 7], [5, 4, 1], [0, 8, 2]], "selected_image": "/images/albums/Birds/1.png"}} \ No newline at end of file diff --git a/main_settings.json b/main_settings.json index eaae2fc..5b6fc6a 100644 --- a/main_settings.json +++ b/main_settings.json @@ -1,5 +1,5 @@ { - "size": "big", + "size": "medium", "style": "classic", "grid": "4x4", "gui color": 0, @@ -10,7 +10,7 @@ }, "sound": { "on": true, - "volume": 16, + "volume": 12, "version": "wood" }, "display": "time",