diff --git a/main.kv b/main.kv index b7b1bd8..c075a8b 100644 --- a/main.kv +++ b/main.kv @@ -1 +1,13 @@ -# kivy v2.1.0 \ No newline at end of file +#:kivy 2.1.0 + +WindowManager: + MenuScreen: + name: "menu" + GameScreen: + name: "game" + SettingsScreen: + name: "settings" + GameOverScreen: + name: "game_over" + AchievementsScreen: + name: "achievements" \ No newline at end of file diff --git a/main.py b/main.py index 8c0d5d5..54a73b5 100644 --- a/main.py +++ b/main.py @@ -1 +1,124 @@ -__version__ = "2.0.0" +""" +Main module of the generator of dialogs. +""" + + +############### +### Imports ### +############### + + +### Python imports ### + +import os + +### Kivy imports ### + +from kivy.app import App +from kivy.uix.screenmanager import ScreenManager, NoTransition, Screen +from kivy.lang import Builder +from kivy.uix.widget import Widget + +### Module imports ### + +from tools.tools_constants import ( + PATH_KIVY_FOLDER, + PATH_IMAGES, + MOBILE_MODE +) +from screens import ( + MenuScreen, + GameScreen, + SettingsScreen, + GameOverScreen, + AchievementsScreen +) +from tools.tools_kivy import ( + color_label, + background_color, + Window +) +from tools.tools_sound import ( + music_mixer +) + + +def change_window_size(*args): + global WINDOW_SIZE, SCREEN_RATIO + + # Compute the size of one tile in pixel + WINDOW_SIZE = Window.size + SCREEN_RATIO = WINDOW_SIZE[0] / WINDOW_SIZE[1] + + +Window.bind(on_resize=change_window_size) +change_window_size() + +# Set the fullscreen +if not MOBILE_MODE: + # Window.fullscreen = "auto" + pass + + +############### +### General ### +############### + + +class WindowManager(ScreenManager): + """ + Screen manager, which allows the navigation between the different menus. + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.gray_color = background_color + self.color_label = color_label + self.transition = NoTransition() + self.add_widget(Screen(name="opening")) + self.current = "opening" + self.list_former_screens = [] + + def init_screen(self, screen_name, *args): + if screen_name == "game": + music_mixer.play("game_music", loop=True) + Window.clearcolor = self.gray_color + self.current = screen_name + self.get_screen(self.current).init_screen(*args) + + +class MainApp(App, Widget): + """ + Main class of the application. + """ + + def build(self): + """ + Build the application. + + Parameters + ---------- + None + + Returns + ------- + None + """ + Window.clearcolor = (0, 0, 0, 1) + self.icon = PATH_IMAGES + "logo.png" + + def on_start(self): + if MOBILE_MODE: + Window.update_viewport() + music_mixer.play("title_music", loop=True) + self.root_window.children[0].init_screen("menu") + return super().on_start() + + +# Run the application +if __name__ == "__main__": + for file_name in os.listdir(PATH_KIVY_FOLDER): + if file_name.endswith(".kv"): + Builder.load_file(PATH_KIVY_FOLDER + file_name, encoding="utf-8") + MainApp().run() + diff --git a/resources/kivy/achievements.kv b/resources/kivy/achievements.kv new file mode 100644 index 0000000..bebae91 --- /dev/null +++ b/resources/kivy/achievements.kv @@ -0,0 +1,52 @@ +#:kivy 2.1.0 +: + + Image: + id: back_image + source: root.path_back_image + size_hint: None,None + width: root.width_back_image + height: root.height_back_image + pos_hint: {"center_x":0.5,"center_y":0.5} + allow_stretch: True + keep_ratio: True + + Image: + source: root.path_images + "back_arrow.png" + size_hint: None, 0.1 + pos_hint: {"x":0.025, "top": 0.975} + allow_stretch: True + width: self.height + Button: + size_hint: None, 0.1 + width: self.height + pos_hint: {"x":0.025, "top": 0.975} + background_color: (0, 0, 0, 0) + on_release: + root.manager.init_screen("menu") + + Label: + text: root.counter_precious_stones + bold: True + color: root.manager.color_label + pos_hint: {"right":1, "top":1} + size_hint: 0.1, 0.1 + font_name: root.font + font_size: 30*root.font_ratio + + RelativeLayout: + size_hint: 1, 0.9 + pos_hint: {"x":0, "y":0} + ScrollView: + id: scroll_view + do_scroll_x: False + do_scroll_y: True + bar_width: 0 + + MyScrollViewLayout: + id: my_sv_layout + cols: root.number_cols + spacing: root.spacing + padding: root.padding + height: self.minimum_height+root.label_height + pos: root.pos diff --git a/resources/kivy/extended_style.kv b/resources/kivy/extended_style.kv new file mode 100644 index 0000000..0568c6b --- /dev/null +++ b/resources/kivy/extended_style.kv @@ -0,0 +1,299 @@ +#:kivy 1.0 + + +######################## +### Colors variables ### +######################## + + +#:set pink_color (229 / 255, 19 / 255, 100 / 255, 1) +#:set blue_color (70 / 255, 130 / 255, 180 / 255, 1) +#:set gray_color (100 / 255, 100 / 255, 100 / 255, 1) +#:set highlight_text_color (229 / 255, 19 / 255, 100 / 255, 0.5) +#:set black_color (0, 0, 0, 1) + + +############### +### Buttons ### +############### + + +<-Button,-ToggleButton>: + + # Blue + background_normal: 'atlas://resources/kivy/images/defaulttheme/button_blue' + # Light pink + background_pressed: 'atlas://resources/kivy/images/defaulttheme/button_pink_pressed' + # Gray + background_disabled: 'atlas://resources/kivy/images/defaulttheme/button_gray_disabled' + # Pink + background_focus: 'atlas://resources/kivy/images/defaulttheme/button_pink' + state_image: (self.background_normal if self.state == 'normal' else self.background_focus) if self.state == 'normal' else self.background_focus + disabled_image: self.background_disabled + bold: True + canvas: + Color: + rgba: self.background_color + BorderImage: + border: self.border + pos: self.pos + size: self.size + source: self.disabled_image if self.disabled else self.state_image + Color: + rgba: 1, 1, 1, 1 + Rectangle: + texture: self.texture + size: self.texture_size + pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.) + + +<-FocusableButton>: + + # Blue + background_normal: 'atlas://resources/kivy/images/defaulttheme/button_blue' + # Light pink + background_pressed: 'atlas://resources/kivy/images/defaulttheme/button_pink_pressed' + # Gray + background_disabled: 'atlas://resources/kivy/images/defaulttheme/button_gray_disabled' + # Pink + background_focus: 'atlas://resources/kivy/images/defaulttheme/button_pink' + state_image: (self.background_focus if self.focus else self.background_normal) if self.state == 'normal' else self.background_pressed + disabled_image: self.background_disabled + bold: True + canvas: + Color: + rgba: self.background_color + BorderImage: + border: self.border + pos: self.pos + size: self.size + source: self.disabled_image if self.disabled else self.state_image + Color: + rgba: 1, 1, 1, 1 + Rectangle: + texture: self.texture + size: self.texture_size + pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.) + + +################## +### Text Input ### +################## + + +: + canvas.before: + Color: + rgba: self.background_color + BorderImage: + border: self.border + pos: self.pos + size: self.size + source: 'atlas://resources/kivy/images/defaulttheme/textinput_active' if self.focus else ('atlas://resources/kivy/images/defaulttheme/textinput_disabled_active' if self.disabled else 'atlas://resources/kivy/images/defaulttheme/textinput') + Color: + rgba: + ((156/255,0,60/255,1) + if self.focus and not self._cursor_blink + and int(self.x + self.padding[0]) <= self._cursor_visual_pos[0] <= int(self.x + self.width - self.padding[2]) + else (0, 0, 0, 0)) + Rectangle: + pos: self._cursor_visual_pos + size: root.cursor_width, -self._cursor_visual_height + Color: + rgba: self.disabled_foreground_color if self.disabled else (self.hint_text_color if not self.text else self.foreground_color) + +: + color: pink_color + allow_stretch: True + +: + color_mode: "custom" + mode: "rectangle" + line_color_focus: pink_color + line_color_normal: blue_color if not self.disabled else gray_color + normal_color: blue_color + color_active: pink_color + disabled_color: gray_color + text_color_focus: black_color + text_color_normal: blue_color + hint_text_color_normal: pink_color + hint_text_color_focus: blue_color + selection_color: highlight_text_color + + +############### +### Spinner ### +############### + + +: + background_normal: 'atlas://resources/kivy/images/defaulttheme/spinner' + background_disabled: 'atlas://resources/kivy/images/defaulttheme/spinner_disabled' + background_focus: 'atlas://resources/kivy/images/defaulttheme/spinner_pressed' + state_image: self.background_focus if self.focus else self.background_normal + disabled_image: self.background_disabled + bold: True + canvas: + Color: + rgba: self.background_color + BorderImage: + border: self.border + pos: self.pos + size: self.size + source: self.disabled_image if self.disabled else self.state_image + Color: + rgba: 1, 1, 1, 1 + Rectangle: + texture: self.texture + size: self.texture_size + pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.) + +: + background_normal: 'atlas://resources/kivy/images/defaulttheme/spinner' + background_disabled_normal: 'atlas://resources/kivy/images/defaulttheme/spinner_disabled' + background_down: 'atlas://resources/kivy/images/defaulttheme/spinner_pressed' + + +############## +### Slider ### +############## + + +: + canvas: + Color: + rgb: 1, 1, 1 + BorderImage: + border: self.border_horizontal if self.orientation == 'horizontal' else self.border_vertical + pos: (self.x + self.padding, self.center_y - self.background_width / 2) if self.orientation == 'horizontal' else (self.center_x - self.background_width / 2, self.y + self.padding) + size: (self.width - self.padding * 2, self.background_width) if self.orientation == 'horizontal' else (self.background_width, self.height - self.padding * 2) + source: ('atlas://resources/kivy/images/defaulttheme/sliderh_background_disabled' if self.orientation == 'horizontal' else 'atlas://resources/kivy/images/defaulttheme/sliderv_background_disabled') if self.disabled else ('atlas://resources/kivy/images/defaulttheme/sliderh_background' if self.orientation == 'horizontal' else 'atlas://resources/kivy/images/defaulttheme/sliderv_background') + Color: + rgba: root.value_track_color if self.value_track and self.orientation == 'horizontal' else [1, 1, 1, 0] + Line: + width: self.value_track_width + points: self.x + self.padding, self.center_y, self.value_pos[0], self.center_y + Color: + rgba: root.value_track_color if self.value_track and self.orientation == 'vertical' else [1, 1, 1, 0] + Line: + width: self.value_track_width + points: self.center_x, self.y + self.padding, self.center_x, self.value_pos[1] + Color: + rgb: 1, 1, 1 + Image: + pos: (root.value_pos[0] - root.cursor_width / 2, root.center_y - root.cursor_height / 2) if root.orientation == 'horizontal' else (root.center_x - root.cursor_width / 2, root.value_pos[1] - root.cursor_height / 2) + size: root.cursor_size + source: 'atlas://resources/kivy/images/defaulttheme/slider_cursor_disabled' if root.disabled else 'atlas://resources/kivy/images/defaulttheme/slider_cursor' + allow_stretch: True + keep_ratio: False + + +#################### +### Progress bar ### +#################### + + +: + canvas: + Color: + rgb: 1, 1, 1 + BorderImage: + border: (12, 12, 12, 12) + pos: self.x, self.center_y - 12 + size: self.width, 24 + source: 'atlas://resources/kivy/images/defaulttheme/progressbar_background' + BorderImage: + border: [int(min(self.width * (self.value / float(self.max)) if self.max else 0, 12))] * 4 + pos: self.x, self.center_y - 12 + size: self.width * (self.value / float(self.max)) if self.max else 0, 24 + source: 'atlas://resources/kivy/images/defaulttheme/progressbar' + + +############## +### Switch ### +############## + + +: + active_norm_pos: max(0., min(1., (int(self.active) + self.touch_distance / sp(41)))) + canvas: + Color: + rgb: 1, 1, 1 + Rectangle: + source: 'atlas://resources/kivy/images/defaulttheme/switch-background{}'.format('_disabled' if self.disabled else '') + size: sp(83), sp(32) + pos: int(self.center_x - sp(41)), int(self.center_y - sp(16)) + canvas.after: + Color: + rgb: 1, 1, 1 + Rectangle: + source: 'atlas://resources/kivy/images/defaulttheme/switch-button{}'.format('_disabled' if self.disabled else '') + size: sp(43), sp(32) + pos: int(self.center_x - sp(41) + self.active_norm_pos * sp(41)), int(self.center_y - sp(16)) + + +################ +### Checkbox ### +################ + + +: + canvas: + Color: + rgba: self.color + Rectangle: + source: (('atlas://resources/kivy/images/defaulttheme/checkbox_radio_focus_on' if self.focus else 'atlas://resources/kivy/images/defaulttheme/checkbox_radio_disabled_on' if self.disabled else 'atlas://resources/kivy/images/defaulttheme/checkbox_radio_on') if self.active else ('atlas://resources/kivy/images/defaulttheme/checkbox_radio_focus_off' if self.focus else 'atlas://resources/kivy/images/defaulttheme/checkbox_radio_disabled_off' if self.disabled else 'atlas://resources/kivy/images/defaulttheme/checkbox_radio_off')) if self.group else (('atlas://resources/kivy/images/defaulttheme/checkbox_focus_on' if self.focus else 'atlas://resources/kivy/images/defaulttheme/checkbox_disabled_on' if self.disabled else 'atlas://resources/kivy/images/defaulttheme/checkbox_on') if self.active else ('atlas://resources/kivy/images/defaulttheme/checkbox_focus_off' if self.focus else 'atlas://resources/kivy/images/defaulttheme/checkbox_disabled_off' if self.disabled else 'atlas://resources/kivy/images/defaulttheme/checkbox_off')) + size: sp(32), sp(32) + pos: int(self.center_x - sp(16)), int(self.center_y - sp(16)) + +: + pos_hint: root.pos_hint + FocusableCheckBox: + id: checkbox + pos_hint: {"x":0, "y":0} + size_hint: root.size_hint_cb + group: root.group + disabled: root.disabled_cb + on_active: + root.function_cb + Label: + text: root.text_label + color: root.color_label + size_hint: root.size_hint_label + pos_hint: {"x":0.1, "y":0} + text_size: self.size + halign: "left" + valign: "center" + +############# +### Popup ### +############# + + +: + size: root.size_popup + pos_hint: {"x":0, "y":0} + + Widget: + canvas: + Color: + rgba: root.blue_color + Rectangle: + pos: root.x, root.top + size: self.width, dp(3) + + +############### +### Tooltip ### +############### + + +: + size_hint: None, None + size: self.texture_size[0]+5, self.texture_size[1]+5 + canvas.before: + Color: + rgb: 0.2, 0.2, 0.2 + Rectangle: + size: self.size + pos: self.pos diff --git a/resources/kivy/game.kv b/resources/kivy/game.kv new file mode 100644 index 0000000..ee3c750 --- /dev/null +++ b/resources/kivy/game.kv @@ -0,0 +1 @@ +#:kivy 2.1.0 \ No newline at end of file diff --git a/resources/kivy/game_over.kv b/resources/kivy/game_over.kv new file mode 100644 index 0000000..29fd209 --- /dev/null +++ b/resources/kivy/game_over.kv @@ -0,0 +1,53 @@ +#:kivy 2.1.0 + +: + Image: + id: back_image + source: root.path_back_image + size_hint: None,None + width: root.width_back_image + height: root.height_back_image + pos_hint: {"center_x":0.5,"center_y":0.5} + allow_stretch: True + keep_ratio: True + + Label: + id: title_label + text: "Game Over" + size_hint: 1, 0.5 + pos_hint: {"center_x": 0.5, "center_y": 0.9} + font_size: 50*root.font_ratio + font_name: root.font + color: root.manager.color_label + + Label: + id: credit_label + text: "Game created by :\n - Agathe\n - Paul" + size_hint: 1, 0.5 + pos_hint: {"center_x": 0.2, "center_y": 0.5} + font_size: 40*root.font_ratio + font_name: root.font + color: root.manager.color_label + + Label: + id: score_label + size_hint: 1, 0.5 + pos_hint: {"center_x": 0.8, "center_y": 0.5} + font_size: 40*root.font_ratio + font_name: root.font + color: root.manager.color_label + + Label: + id: back_to_menu + text: "Back to main menu" + font_name: root.font + font_size: 40*root.font_ratio + color: root.manager.color_label + pos_hint: {"center_x":0.5, "center_y": 0.15} + size_hint: 0.2, 0.1 + Button: + size_hint: 0.2, 0.1 + pos_hint: {"center_x":0.5, "center_y": 0.15} + background_color: (0, 0, 0, 0) + on_release: + root.manager.init_screen("menu") diff --git a/resources/kivy/images/close_button.png b/resources/kivy/images/close_button.png new file mode 100644 index 0000000..9177796 Binary files /dev/null and b/resources/kivy/images/close_button.png differ diff --git a/resources/kivy/images/defaulttheme.atlas b/resources/kivy/images/defaulttheme.atlas new file mode 100644 index 0000000..a2cdc0a --- /dev/null +++ b/resources/kivy/images/defaulttheme.atlas @@ -0,0 +1 @@ +{"defaulttheme.png": {"progressbar_background": [392, 227, 24, 24], "tab_btn_disabled": [332, 137, 32, 32], "tab_btn_pressed": [400, 137, 32, 32], "image-missing": [152, 171, 48, 48], "splitter_h": [174, 123, 32, 7], "splitter_down": [11, 10, 7, 32], "splitter_disabled_down": [2, 10, 7, 32], "vkeyboard_key_down": [2, 44, 32, 32], "vkeyboard_disabled_key_down": [434, 137, 32, 32], "selector_right": [438, 326, 64, 64], "player-background": [2, 287, 103, 103], "selector_middle": [372, 326, 64, 64], "spinner": [204, 82, 29, 37], "tab_btn_disabled_pressed": [366, 137, 32, 32], "switch-button_disabled": [375, 291, 43, 32], "textinput_disabled_active": [134, 221, 64, 64], "splitter_grip": [70, 50, 12, 26], "vkeyboard_key_normal": [36, 44, 32, 32], "button_disabled": [111, 82, 29, 37], "media-playback-stop": [302, 171, 48, 48], "splitter": [502, 137, 7, 32], "splitter_down_h": [140, 123, 32, 7], "sliderh_background_disabled": [115, 132, 41, 37], "modalview-background": [464, 456, 45, 54], "button": [80, 82, 29, 37], "splitter_disabled": [501, 87, 7, 32], "checkbox_radio_focus_on": [467, 87, 32, 32], "slider_cursor": [352, 171, 48, 48], "vkeyboard_disabled_background": [266, 221, 64, 64], "checkbox_focus_on": [331, 87, 32, 32], "sliderv_background_disabled": [41, 78, 37, 41], "button_disabled_pressed": [142, 82, 29, 37], "audio-volume-muted": [102, 171, 48, 48], "close": [487, 173, 20, 20], "action_group_disabled": [2, 121, 33, 48], "vkeyboard_background": [200, 221, 64, 64], "checkbox_off": [365, 87, 32, 32], "tab_disabled": [107, 291, 96, 32], "sliderh_background": [72, 132, 41, 37], "switch-button": [430, 253, 43, 32], "tree_closed": [418, 231, 20, 20], "bubble_btn_pressed": [454, 291, 32, 32], "selector_left": [306, 326, 64, 64], "filechooser_file": [174, 326, 64, 64], "checkbox_radio_focus_off": [433, 87, 32, 32], "checkbox_radio_on": [230, 137, 32, 32], "checkbox_on": [399, 87, 32, 32], "button_pressed": [173, 82, 29, 37], "audio-volume-high": [464, 406, 48, 48], "audio-volume-low": [2, 171, 48, 48], "progressbar": [332, 227, 32, 24], "previous_normal": [488, 291, 19, 32], "separator": [504, 342, 5, 48], "filechooser_folder": [240, 326, 64, 64], "checkbox_radio_off": [196, 137, 32, 32], "textinput_active": [68, 221, 64, 64], "textinput": [2, 221, 64, 64], "player-play-overlay": [122, 395, 117, 115], "media-playback-pause": [202, 171, 48, 48], "sliderv_background": [2, 78, 37, 41], "ring": [354, 402, 108, 108], "bubble_arrow": [490, 241, 16, 10], "slider_cursor_disabled": [402, 171, 48, 48], "checkbox_focus_off": [297, 87, 32, 32], "action_group_down": [37, 121, 33, 48], "spinner_disabled": [235, 82, 29, 37], "splitter_disabled_h": [106, 123, 32, 7], "bubble": [107, 325, 65, 65], "media-playback-start": [252, 171, 48, 48], "vkeyboard_disabled_key_normal": [468, 137, 32, 32], "overflow": [264, 137, 32, 32], "tree_opened": [440, 231, 20, 20], "action_item": [487, 195, 24, 24], "bubble_btn": [420, 291, 32, 32], "audio-volume-medium": [52, 171, 48, 48], "action_group": [452, 171, 33, 48], "spinner_pressed": [266, 82, 29, 37], "filechooser_selected": [2, 392, 118, 118], "tab": [332, 253, 96, 32], "action_bar": [158, 133, 36, 36], "action_view": [366, 227, 24, 24], "tab_btn": [298, 137, 32, 32], "switch-background": [205, 291, 83, 32], "splitter_disabled_down_h": [72, 123, 32, 7], "action_item_down": [475, 253, 32, 32], "switch-background_disabled": [290, 291, 83, 32], "textinput_disabled": [241, 399, 111, 111], "splitter_grip_h": [462, 239, 26, 12], "button_blue": [80, 43, 29, 37], "button_gray_disabled": [111, 43, 29, 37], "button_pink": [142, 43, 29, 37], "button_pink_pressed": [173, 43, 29, 37], "checkbox_disabled_on": [331, 57.5, 32, 32], "checkbox_disabled_off": [297, 57.5, 32, 32], "checkbox_radio_disabled_off":[467, 57.5, 32, 32], "checkbox_radio_disabled_off": [433, 57.5, 32, 32]}} \ No newline at end of file diff --git a/resources/kivy/images/defaulttheme.png b/resources/kivy/images/defaulttheme.png new file mode 100644 index 0000000..9e8e9ac Binary files /dev/null and b/resources/kivy/images/defaulttheme.png differ diff --git a/resources/kivy/menu.kv b/resources/kivy/menu.kv new file mode 100644 index 0000000..4756d9b --- /dev/null +++ b/resources/kivy/menu.kv @@ -0,0 +1,77 @@ +#:kivy 2.1.0 + +: + + Image: + id: back_image + source: root.path_back_image + size_hint: None,None + width: root.width_back_image + height: root.height_back_image + pos_hint: {"center_x":0.5,"center_y":0.5} + allow_stretch: True + keep_ratio: True + + Label: + id: title_label + text: "LUMACRYTE" + size_hint: 1, 0.5 + pos_hint: {"center_x": 0.5, "center_y": 0.85} + font_size: 60*root.font_ratio + font_name: root.font + color: root.manager.color_label + + Label: + id: start_label + text: "Start a new game" if root.mobile_mode else "Press space to start a new game" + size_hint: 1, 0.25 + pos_hint: {"center_x": 0.5, "center_y": 0.45} + font_size: 40*root.font_ratio + font_name: root.font + color: root.manager.color_label + Button: + size_hint: 1, 0.25 + pos_hint: {"center_x": 0.5, "center_y": 0.45} + background_color: 0, 0, 0, 0 + on_release: + root.manager.init_screen("world_explorer") + + Image: + id: settings_logo + source: root.path_images + "settings_logo.png" + size_hint: None, 0.15 + pos_hint: {"right": 0.95, "y": 0.05} + allow_stretch: True + width: self.height + Button: + id: settings_button + size_hint: None, 0.15 + pos_hint: {"right": 0.95, "y": 0.05} + background_color: (0, 0, 0, 0) + width: self.height + on_release: + root.manager.init_screen("settings") + + Label: + id: high_score_label + text: root.high_score + bold: True + color: root.manager.color_label + pos_hint: {"right":1, "y":0} + size_hint: 0.2, 0.1 + font_name: root.font + font_size: 30*root.font_ratio + + Image: + source: root.path_images + "collection_logo.png" + size_hint: None, 0.15 + pos_hint: {"x": 0.05, "y": 0.05} + allow_stretch: True + width: self.height + Button: + size_hint: None, 0.15 + pos_hint: {"x": 0.05, "y": 0.05} + background_color: (0, 0, 0, 0) + width: self.height + on_release: + root.manager.init_screen("collection") diff --git a/resources/kivy/settings.kv b/resources/kivy/settings.kv new file mode 100644 index 0000000..989fae2 --- /dev/null +++ b/resources/kivy/settings.kv @@ -0,0 +1,140 @@ +#:kivy 2.1.0 + +: + + Image: + id: back_image + source: root.path_back_image + size_hint: None,None + width: root.width_back_image + height: root.height_back_image + pos_hint: {"center_x":0.5,"center_y":0.5} + allow_stretch: True + keep_ratio: True + + Image: + source: root.path_images + "back_arrow.png" + size_hint: None, 0.1 + pos_hint: {"x":0.025, "top": 0.975} + allow_stretch: True + width: self.height + Button: + size_hint: None, 0.1 + width: self.height + pos_hint: {"x":0.025, "top": 0.975} + background_color: (0, 0, 0, 0) + on_release: + root.manager.init_screen("menu") + + Label: + text: root.high_score + bold: True + color: root.manager.color_label + pos_hint: {"right":1, "top":1} + size_hint: 0.2, 0.1 + font_name: root.font + font_size: 30*root.font_ratio + + + # KEYBOARD CONFIGURATION + + Button: + id: top_button + text: "Move up" + size_hint: 0.15, 0.1 + pos_hint: {"x":0.45, "y":0.7} + color: root.manager.gray_color + font_name: root.font + font_size: 20*root.font_ratio + on_release: + root.select_next_key("top") + Button: + id: top_key_input + text: root.top_key + size_hint: 0.1, 0.1 + pos_hint: {"x":0.65, "y": 0.7} + disabled: True + font_name: root.font + font_size: 20*root.font_ratio + color: root.manager.color_label + + Button: + id: left_button + text: "Move left" + size_hint: 0.15, 0.1 + pos_hint: {"x":0.45, "y":0.55} + color: root.manager.gray_color + font_size: 20*root.font_ratio + font_name: root.font + on_release: + root.select_next_key("left") + Button: + id: left_key_input + text: root.left_key + size_hint: 0.1, 0.1 + pos_hint: {"x":0.65, "y": 0.55} + disabled: True + font_name: root.font + font_size: 20*root.font_ratio + color: root.manager.color_label + + Button: + id: bottom_button + text: "Move down" + size_hint: 0.15, 0.1 + pos_hint: {"x":0.45, "y":0.4} + color: root.manager.gray_color + font_size: 20*root.font_ratio + font_name: root.font + on_release: + root.select_next_key("bottom") + Button: + id: bottom_key_input + text: root.bottom_key + size_hint: 0.1, 0.1 + pos_hint: {"x":0.65, "y": 0.4} + disabled: True + font_name: root.font + font_size: 20*root.font_ratio + color: root.manager.color_label + + + Button: + id: right_button + text: "Move right" + size_hint: 0.15, 0.1 + pos_hint: {"x":0.45, "y":0.25} + color: root.manager.gray_color + font_size: 20*root.font_ratio + font_name: root.font + on_release: + root.select_next_key("right") + Button: + id: right_key_input + text: root.right_key + size_hint: 0.1, 0.1 + disabled: True + font_name: root.font + font_size: 20*root.font_ratio + pos_hint: {"x":0.65, "y": 0.25} + color: root.manager.color_label + + Button: + id: interact_button + text: "Interact" + size_hint: 0.15, 0.1 + pos_hint: {"x":0.45, "y":0.1} + color: root.manager.gray_color + font_size: 20*root.font_ratio + font_name: root.font + on_release: + root.select_next_key("INTERACT") + Button: + id: interact_key_input + text: root.interact_key + size_hint: 0.1, 0.1 + disabled: True + font_name: root.font + font_size: 20*root.font_ratio + pos_hint: {"x":0.65, "y": 0.1} + color: root.manager.color_label diff --git a/resources/musics/game_music.mp3 b/resources/musics/game_music.mp3 new file mode 100644 index 0000000..58360cc Binary files /dev/null and b/resources/musics/game_music.mp3 differ diff --git a/resources/musics/game_over_music.mp3 b/resources/musics/game_over_music.mp3 new file mode 100644 index 0000000..cf23c77 Binary files /dev/null and b/resources/musics/game_over_music.mp3 differ diff --git a/resources/musics/title_music.mp3 b/resources/musics/title_music.mp3 new file mode 100644 index 0000000..1f5dd7e Binary files /dev/null and b/resources/musics/title_music.mp3 differ diff --git a/resources/sounds/darkness.mp3 b/resources/sounds/darkness.mp3 new file mode 100644 index 0000000..a20829d Binary files /dev/null and b/resources/sounds/darkness.mp3 differ diff --git a/resources/sounds/death.mp3 b/resources/sounds/death.mp3 new file mode 100644 index 0000000..fa36e7d Binary files /dev/null and b/resources/sounds/death.mp3 differ diff --git a/resources/sounds/flic.mp3 b/resources/sounds/flic.mp3 new file mode 100644 index 0000000..afffe83 Binary files /dev/null and b/resources/sounds/flic.mp3 differ diff --git a/resources/sounds/get_crystal.mp3 b/resources/sounds/get_crystal.mp3 new file mode 100644 index 0000000..7dff128 Binary files /dev/null and b/resources/sounds/get_crystal.mp3 differ diff --git a/resources/sounds/give_crystal.mp3 b/resources/sounds/give_crystal.mp3 new file mode 100644 index 0000000..1866aec Binary files /dev/null and b/resources/sounds/give_crystal.mp3 differ diff --git a/resources/sounds/near_beacon.mp3 b/resources/sounds/near_beacon.mp3 new file mode 100644 index 0000000..1c943c8 Binary files /dev/null and b/resources/sounds/near_beacon.mp3 differ diff --git a/resources/sounds/near_crystal.mp3 b/resources/sounds/near_crystal.mp3 new file mode 100644 index 0000000..c9e1895 Binary files /dev/null and b/resources/sounds/near_crystal.mp3 differ diff --git a/resources/sounds/start_beacon.mp3 b/resources/sounds/start_beacon.mp3 new file mode 100644 index 0000000..e2789e0 Binary files /dev/null and b/resources/sounds/start_beacon.mp3 differ diff --git a/screens/__init__.py b/screens/__init__.py new file mode 100644 index 0000000..4fcc8be --- /dev/null +++ b/screens/__init__.py @@ -0,0 +1,5 @@ +from screens.menu import MenuScreen +from screens.settings import SettingsScreen +from screens.game import GameScreen +from screens.game_over import GameOverScreen +from screens.achievements import AchievementsScreen \ No newline at end of file diff --git a/screens/achievements.py b/screens/achievements.py new file mode 100644 index 0000000..9fe38b9 --- /dev/null +++ b/screens/achievements.py @@ -0,0 +1,114 @@ +""" +Module for the collection menu +""" + +# TODO +# Fins débloquées +# Highscore + +############### +### Imports ### +############### + + +from kivy.uix.screenmanager import Screen +from kivy.uix.image import Image +from kivy.uix.label import Label +from kivy.uix.relativelayout import RelativeLayout +from kivy.core.window import Window +from kivy.properties import StringProperty, ObjectProperty, NumericProperty +from tools.tools_constants import ( + PATH_TITLE_FONT, + PATH_IMAGES +) + + +################ +### Constant ### +################ + + +global_spacing = { + "horizontal": Window.size[0] / 50, + "vertical": Window.size[1] / 50 +} + + +############# +### Class ### +############# + + +class AchievementsScreen(Screen): + def __init__(self, **kw): + super().__init__(**kw) + + font = PATH_TITLE_FONT + path_images = PATH_IMAGES + counter_precious_stones = StringProperty("") + number_cols = 7 + spacing = global_spacing["horizontal"] + label_height = 40 * 7 * Window.size[1] / 2340 + padding = [0.05 * Window.size[0], 0, 0.05 * Window.size[0], 0] + path_back_image = PATH_IMAGES + "collection_background.png" + font_ratio = NumericProperty(0) + width_back_image = ObjectProperty(Window.size[0]) + height_back_image = ObjectProperty(Window.size[0] * 392 / 632) + + def init_screen(self): + pass + # self.font_ratio = Window.size[0]/800 + # self.width_back_image = Window.size[0] + # self.height_back_image = Window.size[0] * 392 / 632 + # self.label_height = 40 * 7 * Window.size[1] / 2340 + # self.padding = [0.05 * Window.size[0], 0, 0.05 * Window.size[0], 0] + # self.ids.my_sv_layout.reset_screen() + # number_precious_stones = 0 + # for stone in my_collection.dict_collection: + # if my_collection.dict_collection[stone]: + # number_precious_stones += 1 + # number_total_precious_stones = len( + # my_collection.dict_collection.keys()) + # self.counter_precious_stones = str(number_precious_stones) + " / " + str( + # number_total_precious_stones) + # self.build_scroll_view() + + # def build_scroll_view(self): + # TEXTURE_DICT = load_textures_from_atlas("map_textures") + # image_dimension = (Window.size[0] - 2 * self.padding[0] - self.spacing * ( + # self.number_cols - 1)) / self.number_cols + # height_layout = image_dimension + self.label_height + # for code_precious_stone in DICT_TREASURE_STONES: + # name_image = DICT_TREASURE_STONES[code_precious_stone] + # if my_collection.dict_collection[name_image]: + # relative_layout = RelativeLayout( + # size_hint=(None, None), + # height=height_layout, + # width=image_dimension + # ) + # # Label for the name of the gallery + # name_displayed = name_image + # if name_image == "Tiger eye": + # name_displayed = "Tiger's eye" + # name_label = Label( + # size_hint=(None, None), + # width=image_dimension, + # height=self.label_height, + # color=self.manager.color_label, + # text=name_displayed, + # pos_hint={"x": 0, "y": 0}, + # font_name=self.font, + # font_size=25*self.font_ratio + # ) + # relative_layout.add_widget(name_label) + # # Image + # image = Image( + # size_hint=(None, None), + # width=image_dimension, + # height=image_dimension, + # texture=TEXTURE_DICT[DICT_TREASURE_STONES[code_precious_stone]], + # allow_stretch=True, + # y=self.label_height + # ) + # relative_layout.add_widget(image) + # self.ids.my_sv_layout.add_widget(relative_layout) diff --git a/screens/game.py b/screens/game.py new file mode 100644 index 0000000..114eb08 --- /dev/null +++ b/screens/game.py @@ -0,0 +1,43 @@ +""" +Module for the game over screen +""" + + +from kivy.uix.screenmanager import Screen +from kivy.clock import Clock +from kivy.core.window import Window +from tools.tools_constants import ( + PATH_TITLE_FONT, + PATH_IMAGES, + FPS, + OPACITY_RATE +) +from kivy.properties import StringProperty, ObjectProperty, NumericProperty +from tools.tools_sound import music_mixer + + +class GameScreen(Screen): + def __init__(self, **kw): + super().__init__(**kw) + + font = PATH_TITLE_FONT + path_back_image = PATH_IMAGES + "game_over_background.png" + width_back_image = ObjectProperty(Window.size[0]) + height_back_image = ObjectProperty(Window.size[0] * 392 / 632) + opacity_state = - 1 + font_ratio = NumericProperty(0) + score_str = StringProperty("") + + def init_screen(self, *args): + self.font_ratio = Window.size[0]/800 + self.width_back_image = Window.size[0] + self.height_back_image = Window.size[0] * 392 / 632 + music_mixer.play("game_over_music", loop=True) + score_str = f"Score: {int(args[0])}" + self.ids.score_label.text = score_str + Clock.schedule_interval(self.update, 1 / FPS) + + def update(self, *args): + self.ids.back_to_menu.opacity += self.opacity_state * OPACITY_RATE + if self.ids.back_to_menu.opacity < 0 or self.ids.back_to_menu.opacity > 1: + self.opacity_state = -self.opacity_state diff --git a/screens/game_over.py b/screens/game_over.py new file mode 100644 index 0000000..76f5893 --- /dev/null +++ b/screens/game_over.py @@ -0,0 +1,43 @@ +""" +Module for the game over screen +""" + + +from kivy.uix.screenmanager import Screen +from kivy.clock import Clock +from kivy.core.window import Window +from tools.tools_constants import ( + PATH_TITLE_FONT, + PATH_IMAGES, + FPS, + OPACITY_RATE +) +from kivy.properties import StringProperty, ObjectProperty, NumericProperty +from tools.tools_sound import music_mixer + + +class GameOverScreen(Screen): + def __init__(self, **kw): + super().__init__(**kw) + + font = PATH_TITLE_FONT + path_back_image = PATH_IMAGES + "game_over_background.png" + width_back_image = ObjectProperty(Window.size[0]) + height_back_image = ObjectProperty(Window.size[0] * 392 / 632) + opacity_state = - 1 + font_ratio = NumericProperty(0) + score_str = StringProperty("") + + def init_screen(self, *args): + self.font_ratio = Window.size[0]/800 + self.width_back_image = Window.size[0] + self.height_back_image = Window.size[0] * 392 / 632 + music_mixer.play("game_over_music", loop=True) + score_str = f"Score: {int(args[0])}" + self.ids.score_label.text = score_str + Clock.schedule_interval(self.update, 1 / FPS) + + def update(self, *args): + self.ids.back_to_menu.opacity += self.opacity_state * OPACITY_RATE + if self.ids.back_to_menu.opacity < 0 or self.ids.back_to_menu.opacity > 1: + self.opacity_state = -self.opacity_state diff --git a/screens/menu.py b/screens/menu.py new file mode 100644 index 0000000..23f6c59 --- /dev/null +++ b/screens/menu.py @@ -0,0 +1,67 @@ +""" +Module for the main menu +""" + + +from kivy.uix.screenmanager import Screen +from kivy.clock import Clock +from kivy.core.window import Window +from kivy.properties import ( + StringProperty, + BooleanProperty, + ObjectProperty, + NumericProperty +) + +from tools.tools_constants import ( + PATH_IMAGES, + FPS, + OPACITY_RATE, + PATH_TITLE_FONT, + MOBILE_MODE +) +from tools.tools_sound import music_mixer + + +class MenuScreen(Screen): + def __init__(self, **kw): + super().__init__(**kw) + + self.opacity_state = -1 + + path_images = PATH_IMAGES + font = PATH_TITLE_FONT + path_back_image = PATH_IMAGES + "menu_background.png" + font_ratio = NumericProperty(0) + width_back_image = ObjectProperty(Window.size[0]) + height_back_image = ObjectProperty(Window.size[0] * 392 / 632) + mobile_mode = BooleanProperty(True) + high_score = StringProperty("") + + def init_screen(self): + self.mobile_mode = MOBILE_MODE + self.font_ratio = Window.size[0]/800 + self.width_back_image = Window.size[0] + self.height_back_image = Window.size[0] * 392 / 632 + if music_mixer.musics["title_music"].state != "play": + music_mixer.play("title_music", loop=True) + + try: + if MOBILE_MODE: + self.remove_widget(self.ids.settings_logo) + self.remove_widget(self.ids.settings_button) + else: + self.remove_widget(self.ids.high_score_label) + except: + pass + + try: + Clock.unschedule(self.update) + except: + pass + Clock.schedule_interval(self.update, 1 / FPS) + + def update(self, *args): + self.ids.start_label.opacity += self.opacity_state * OPACITY_RATE + if self.ids.start_label.opacity < 0 or self.ids.start_label.opacity > 1: + self.opacity_state = -self.opacity_state diff --git a/screens/settings.py b/screens/settings.py new file mode 100644 index 0000000..bd54385 --- /dev/null +++ b/screens/settings.py @@ -0,0 +1,42 @@ +""" +Module for the settings menu +""" + +# TODO +# Changement de langue (fr en) +# Réglage volume musique bruitage +# Désactiver les pubs + + +from kivy.uix.screenmanager import Screen +from kivy.properties import StringProperty, ObjectProperty, NumericProperty +from kivy.core.window import Window +from tools.tools_constants import ( + PATH_TITLE_FONT, + PATH_IMAGES +) + + +class SettingsScreen(Screen): + def __init__(self, **kw): + super().__init__(**kw) + + path_images = PATH_IMAGES + font = PATH_TITLE_FONT + high_score = StringProperty("") + top_key = StringProperty() + left_key = StringProperty("") + bottom_key = StringProperty("") + right_key = StringProperty("") + interact_key = StringProperty("") + path_back_image = PATH_IMAGES + "settings_background.png" + font_ratio = NumericProperty(0) + width_back_image = ObjectProperty(Window.size[0]) + height_back_image = ObjectProperty(Window.size[0] * 392 / 632) + + def init_screen(self): + self.font_ratio = Window.size[0]/800 + self.width_back_image = Window.size[0] + self.height_back_image = Window.size[0] * 392 / 632 + pass + diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000..3e7866d --- /dev/null +++ b/tools/__init__.py @@ -0,0 +1,20 @@ +""" +Tools package of the application. + +Modules +------- +tools_basis + Module containing basis functions. + +tools_constants + Module referencing the main constants of the application. + +tools_kivy + Module for general Kivy widgets and functions. + +tools_settings + Module dealing with the settings of the application. +""" + +from tools.tools_basis import * +from tools.tools_constants import * diff --git a/tools/tools_basis.py b/tools/tools_basis.py new file mode 100644 index 0000000..9d6408b --- /dev/null +++ b/tools/tools_basis.py @@ -0,0 +1,64 @@ +""" +Module containing basis functions. + +Functions +--------- +load_json_file + Load a json file, according the specified path. + +save_json_file + Save the content of the given dictionnary inside the specified json file. +""" + + +############### +### Imports ### +############### + + +### Python imports ### + +import json + + +################# +### Functions ### +################# + + +def load_json_file(file_path: str) -> dict: + """ + Load a json file, according the specified path. + + Parameters + ---------- + file_path : str + Path of the json file + + Returns + ------- + dict + Content of the json file + """ + with open(file_path, "r", encoding="utf-8") as file: + res = json.load(file) + return res + +def save_json_file(file_path: str, dict_to_save: dict) -> None: + """ + Save the content of the given dictionnary inside the specified json file. + + Parameters + ---------- + file_path : str + Path of the json file + + dict_to_save : dict + Dictionnary to save + + Returns + ------- + None + """ + with open(file_path, "w", encoding="utf-8") as file: + json.dump(dict_to_save, file, indent=4) diff --git a/tools/tools_constants.py b/tools/tools_constants.py new file mode 100644 index 0000000..bde18c4 --- /dev/null +++ b/tools/tools_constants.py @@ -0,0 +1,125 @@ +""" +Module referencing the main constants of the application. + +Constants +--------- +__version__ : str + Version of the application. + +MOBILE_MODE : bool + Whether the application is launched on mobile or not. + +APPLICATION_NAME : str + Name of the application. + +PATH_DATA_FOLDER : str + Path to the data folder. + +PATH_SETTINGS : str + Path to the json file of settings. + +PATH_RESOURCES_FOLDER : str + Path to the resources folder. + +PATH_LANGUAGE : str + Path to the folder where are stored the json files of language. + +PATH_APP_IMAGES : str + Path to the folder where are stored the images for the application. + +PATH_KIVY_FOLDER : str + Path to the folder where are stored the different kv files. + +DICT_LANGUAGE_FONT : dict + Dictionary associating the code of language to the font. + +DICT_LANGUAGE_CORRESPONDANCE : dict + Dictionary associating the language to its code. +""" + + +############### +### Imports ### +############### + + +### Kivy imports ### + +from kivy import platform +from kivy.config import Config + + +from tools.tools_basis import ( + load_json_file +) + + +################# +### Constants ### +################# + + +### Version ### + +__version__ = "2.0.0" + +### Mode ### + +MOBILE_MODE = platform == "android" +DEBUG_MODE = False + +### Kivy parameters ### + +FPS = 30 +OPACITY_RATE = 0.02 +MSAA_LEVEL = 4 + +Config.set("graphics", "maxfps", FPS) +Config.set("graphics", "multisamples", MSAA_LEVEL) + +### Paths ### + +PATH_DATA_FOLDER = "data/" +PATH_SETTINGS = PATH_DATA_FOLDER + "settings.json" + +PATH_RESOURCES_FOLDER = "resources/" +PATH_LANGUAGE = PATH_RESOURCES_FOLDER + "languages/" +PATH_IMAGES = PATH_RESOURCES_FOLDER + "images/" +PATH_MAP_TEXTURES = PATH_IMAGES + "map_textures/" +PATH_ATLAS = PATH_RESOURCES_FOLDER + "atlas/" +PATH_KIVY_FOLDER = PATH_RESOURCES_FOLDER + "kivy/" +PATH_MAPS = PATH_RESOURCES_FOLDER + "maps/" +PATH_CHARACTER_IMAGES = PATH_IMAGES + "ghost_textures/" +PATH_LOGO = PATH_RESOURCES_FOLDER + "start_logo/" +PATH_SOUNDS = PATH_RESOURCES_FOLDER + "sounds/" +PATH_MUSICS = PATH_RESOURCES_FOLDER + "musics/" +PATH_FONTS = PATH_RESOURCES_FOLDER + "fonts/" +PATH_TITLE_FONT = PATH_FONTS + "enchanted_land/Enchanted Land.otf" +SETTINGS = load_json_file(PATH_SETTINGS) + +### Language ### + +DICT_LANGUAGE_FONT = { + "french": "Roboto", + "english": "Roboto" +} + +DICT_LANGUAGE_CORRESPONDANCE = { + "french": "Français", + "english": "English" +} + + +############# +### Sound ### +############# + + +SOUND_VOLUME = 0.5 +MUSIC_VOLUME = 0.5 + +SOUND_RADIUS_CRYSTAL = 3 +SOUND_RADIUS_BEACON = 3 +PROBABILITY_WATER_DROPS = 0.005 + +GAME_OVER_FREEZE_TIME = 2 diff --git a/tools/tools_kivy.py b/tools/tools_kivy.py new file mode 100644 index 0000000..7b52c61 --- /dev/null +++ b/tools/tools_kivy.py @@ -0,0 +1,409 @@ +""" +Module for general Kivy widgets and functions. +""" + +############### +### Imports ### +############### + + +### Kivy imports ### + +from kivy.metrics import dp +from kivy.uix.button import Button +from kivy.uix.image import Image +from kivy.uix.textinput import TextInput +from kivy.uix.label import Label +from kivy.uix.progressbar import ProgressBar +from kivy.uix.checkbox import CheckBox +from kivy.uix.spinner import Spinner +from kivy.uix.popup import Popup +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.gridlayout import GridLayout +from kivy.uix.behaviors import FocusBehavior +from kivy.core.window import Window +from kivy.clock import Clock +from kivy.properties import StringProperty, ObjectProperty +from kivy.compat import string_types +from kivy.factory import Factory + +### Python imports ### + +from functools import partial + +### Modules imports ### + +from tools.tools_constants import ( + PATH_KIVY_FOLDER +) +my_language = {} + + +######################## +### Global Variables ### +######################## + + +### Kivy theme ### + +window_size = Window.size +#window_size = (900, 500) +size_popup = (int(window_size[0] / 1.2), int(window_size[1] / 2)) +global_spacing = { + "horizontal": window_size[0] / 50, + "vertical": window_size[1] / 50 +} +background_color = (70 / 255, 65 / 255, 62 / 255, 1) +color_label = (254 / 255, 195 / 255, 3 / 255, 1) +color_label_popup = (1, 1, 1, 1) +blue_color = (70 / 255, 130 / 255, 180 / 255, 1) +pink_color = (229 / 255, 19 / 255, 100 / 255, 1) +highlight_text_color = (229 / 255, 19 / 255, 100 / 255, 0.5) + +scale_image = 3 * window_size[1] / 2340 + + +##################### +### Popup windows ### +##################### + + +def blank_function(*args, **kwargs): + """ + Function that does nothing + """ + pass + + +class ImprovedPopupLayout(FloatLayout): + """ + Class used to make the background of the popup with the pink line + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + pink_color = pink_color + blue_color = blue_color + size_popup = size_popup + + +class ImprovedPopup(Popup): + """ + Class used to easily create popups + """ + + def __init__(self, title="Popup", size_hint=(None, None), auto_dismiss=False, add_content=[], size=size_popup, font="Roboto"): + + # Initialisation du layout contenant les objets du popup + self.layout = ImprovedPopupLayout() + # Initialisation du popup par héritage + super().__init__(title=title, size_hint=size_hint, + auto_dismiss=auto_dismiss, content=self.layout, size=size, title_font=font) + # Ajout du bouton de fermeture + self.add_close_button() + # Ajout des composants voulus + correspondance_list = [ + ("label", self.add_label), + ("text_input", self.add_text_input), + ("spinner", self.add_spinner), + ("progress_bar", self.add_progress_bar), + ("button", self.add_button), + ("widget", self.add_other_widget) + ] + for instruction in add_content: + for i in range(len(correspondance_list)): + if instruction[0] == correspondance_list[i][0]: + correspondance_list[i][1](**instruction[1]) + # Ouverture du popup + self.open() + + def add_close_button(self): + pos_hint = {"right": 1, "y": 1 + 8 / self.height} + size = (dp(32), dp(32)) + size_hint = (None, None) + close_button = Button( + background_color=(0, 0, 0, 0), + pos_hint=pos_hint, + size_hint=size_hint, + size=size + ) + close_button.on_release = self.dismiss + close_button_image = Image( + source=PATH_KIVY_FOLDER + "images/close_button.png", + pos_hint=pos_hint, + size_hint=size_hint, + size=size + ) + self.layout.add_widget(close_button) + self.layout.add_widget(close_button_image) + + def add_label(self, text="", size_hint=(0.6, 0.2), pos_hint={"x": 0.2, "top": 0.9}, bool_text_size=False, **kwargs): + label = Label( + text=text, + size_hint=size_hint, + pos_hint=pos_hint, + shorten=False, + text_size=( + int(size_hint[0] * Window.size[0] * 0.95), None), + **kwargs) + if bool_text_size: + label.text_size = label.size + label.halign = "left" + label.valign = "center" + else: + label.halign = "center" + self.layout.add_widget(label) + return label + + def add_text_input(self, text="", pos_hint={"x": 0.1, "top": 0.7}, size_hint=(0.8, 0.2), multiline=False, **kwargs): + text_input = TextInput(text=text, + size_hint=size_hint, + pos_hint=pos_hint, + selection_color=highlight_text_color, + multiline=multiline, **kwargs) + self.layout.add_widget(text_input) + + def add_spinner(self, text="Spinner", values=["Spin 1", "Spin 2"], size_hint=(0.6, 0.2), pos_hint={"x": 0.2, "top": 0.7}, halign="center", **kwargs): + spinner = Spinner(text=text, + values=values, + size_hint=size_hint, + pos_hint=pos_hint, + halign=halign, **kwargs) + self.layout.add_widget(spinner) + + def add_progress_bar(self, max=100, pos_hint={"center_x": 0.5, "top": 0.85}, size_hint=(0.5, 0.25), **kwargs): + progress_bar = ProgressBar( + max=max, + pos_hint=pos_hint, + size_hint=size_hint, + **kwargs) + # Set progress_bar as property in order to change its value later on + self.progress_bar = progress_bar + self.layout.add_widget(progress_bar) + return progress_bar + + def add_button(self, text="", disabled=False, size_hint=(0.8, 0.2), pos_hint={"x": 0.1, "top": 0.45}, halign="center", on_release=blank_function, **kwargs): + button = FocusableButton( + text=text, + size_hint=size_hint, + pos_hint=pos_hint, + halign=halign, + disabled=disabled, + **kwargs) + button.on_release = on_release + self.layout.add_widget(button) + return button + + def add_checkbox(self, text="", color_label=color_label_popup, + size_hint_cb=(0.05, 0.05), pos_hint={"x": 0.1, "y": 0}, + group=None, size_hint_label=(0.05, 0.1), + function_cb=blank_function, + disabled=False, **kwargs): + checkbox = LabelledCheckBox( + text_label=text, + color_label=color_label, + size_hint_label=size_hint_label, + size_hint_cb=size_hint_cb, + pos_hint=pos_hint, + group=group, + function_cb=function_cb, + disabled=disabled, + **kwargs) + self.layout.add_widget(checkbox) + return checkbox + + def add_other_widget(self, widget_class, **kwargs): + widget = widget_class(**kwargs) + self.layout.add_widget(widget) + return widget + + +# def create_standard_popup(title_popup, message, button_message=my_language.dict_buttons["close"]): +# popup_content = [ +# ("label", { +# "text": message, +# "pos_hint": {"x": 0.1, "y": 0.6}, +# "size_hint": (0.8, 0.15) +# }) +# ] +# popup = ImprovedPopup( +# title=title_popup, +# add_content=popup_content) +# button = popup.add_button( +# text=button_message, +# pos_hint={"x": 0.2, "y": 0.25}, +# size_hint=(0.6, 0.15) +# ) +# button.on_release = popup.dismiss +# button.focus = True + + +#################### +### Scroll views ### +#################### + + +class MyScrollViewLayout(GridLayout): + """ + Class corresponding to the layout inside the scroll view + """ + + def __init__(self, **kwargs): + super(MyScrollViewLayout, self).__init__(**kwargs) + self.size_hint_y = (None) + self.bind(minimum_height=self.setter('height')) + + def reset_screen(self): + list_widgets = self.children[:] + for element in list_widgets: + self.remove_widget(element) + + +####################### +### Focusable items ### +####################### + + +class FocusableSpinner(FocusBehavior, Spinner): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def on_is_open(self, instance, value): + if self.is_open: + self._dropdown.clear_widgets() + for value in self.values: + btn = FocusableButton( + text=value, + size_hint_y=None, + height=self.height, + font_name=self.font_name + ) + btn.on_release = partial(self.on_button_press, btn) + self._dropdown.add_widget(btn) + return super().on_is_open(instance, value) + + def on_button_press(self, button): + self._dropdown.select(button.text) + + def keyboard_on_key_down(self, window, keycode, text, modifiers): + key = keycode[-1] + if key in ("spacebar", "enter"): + self.is_open = not self.is_open + if self.is_open: + self._dropdown.children[0].children[-1].focus = True + + return super(FocusableSpinner, self).keyboard_on_key_down(window, keycode, text, modifiers) + + +class ToolTip(Label): + pass + + +class FocusableButton(FocusBehavior, Button): + tooltip_text = StringProperty('') + tooltip_cls = ObjectProperty(ToolTip) + + def __init__(self, scroll_to=False, **kwargs): + self._tooltip = None + self.scroll_to = scroll_to + super().__init__(**kwargs) + fbind = self.fbind + fbind('tooltip_cls', self._build_tooltip) + fbind('tooltip_text', self._update_tooltip) + Window.bind(mouse_pos=self.on_mouse_pos) + self._build_tooltip() + + def _build_tooltip(self, *largs): + if self._tooltip: + self._tooltip = None + cls = self.tooltip_cls + if isinstance(cls, string_types): + cls = Factory.get(cls) + self._tooltip = cls() + self._update_tooltip() + + def _update_tooltip(self, *largs): + self._tooltip.text = self.tooltip_text + + def keyboard_on_key_down(self, window, keycode, text, modifiers): + key = keycode[-1] + if key in ("spacebar", "enter"): + self.on_release() + + return super(FocusableButton, self).keyboard_on_key_down(window, keycode, text, modifiers) + + def on_mouse_pos(self, *args): + if self.tooltip_text != "": + if not self.get_root_window(): + return + pos = args[1] + self._tooltip.pos = pos + # cancel scheduled event since I moved the cursor + Clock.unschedule(self.display_tooltip) + self.close_tooltip() # close if it's opened + if self.collide_point(*self.to_widget(*pos)): + Clock.schedule_once(self.display_tooltip, 1) + + def close_tooltip(self, *args): + Window.remove_widget(self._tooltip) + + def display_tooltip(self, *args): + Window.add_widget(self._tooltip) + + def _on_focus(self, instance, value, *largs): + if self.scroll_to: + if (self.parent.number_lines + 1) * self.parent.size_line > self.parent.parent.height: + self.parent.parent.scroll_to(self) + return super()._on_focus(instance, value, *largs) + + +class FocusableCheckBox(FocusBehavior, CheckBox): + def __init__(self, scroll_to=False, **kwargs): + self.scroll_to = scroll_to + super().__init__(**kwargs) + + def keyboard_on_key_down(self, window, keycode, text, modifiers): + key = keycode[-1] + if key in ("spacebar", "enter"): + self.active = not self.active + return super(FocusableCheckBox, self).keyboard_on_key_down(window, keycode, text, modifiers) + + def _on_focus(self, instance, value, *largs): + if self.scroll_to: + if (self.parent.number_lines + 1) * self.parent.size_line > self.parent.parent.height: + self.parent.parent.scroll_to(self) + return super()._on_focus(instance, value, *largs) + + +class LabelledCheckBox(FloatLayout): + def __init__(self, text_label="", + size_hint_label=(0.05, 0.1), + color_label=(0, 0, 0, 1), + pos_hint={"x": 0, "y": 0}, + size_hint_cb=(0.05, 0.05), + function_cb=blank_function, + disabled=False, + group=None, **kwargs): + self.text_label = text_label + self.size_hint_label = size_hint_label + self.color_label = color_label + self.pos_hint = pos_hint + self.size_hint_cb = size_hint_cb + self.function_cb = function_cb + self.group = group + self.disabled_cb = disabled + super().__init__(**kwargs) + + +class FocusableTextInput(TextInput): + def __init__(self, scroll_to=False, **kwargs): + self.scroll_to = scroll_to + self.write_tab = False + super().__init__(**kwargs) + self.last_value = self.text + + def _on_focus(self, instance, value, *largs): + if self.scroll_to: + if (self.parent.number_lines + 1) * self.parent.size_line > self.parent.parent.height: + self.parent.parent.scroll_to(self) + return super()._on_focus(instance, value, *largs) diff --git a/tools/tools_kivy_mobile.py b/tools/tools_kivy_mobile.py new file mode 100644 index 0000000..ef439d9 --- /dev/null +++ b/tools/tools_kivy_mobile.py @@ -0,0 +1,197 @@ +""" + +""" + + +############### +### Imports ### +############### + +### Python imports ### + +from math import sin, cos, asin, acos, sqrt, pi, pow, ceil + +### Kivy imports ### + +from kivy.graphics import Rectangle, Color, Line +from kivy.uix.relativelayout import RelativeLayout +from kivy.uix.widget import Widget + +### Module imports ### + +from tools.tools_constants import ( + Window +) + + +def change_window_size(*args): + global WINDOW_SIZE, SCREEN_RATIO + + # Compute the size of one tile in pixel + WINDOW_SIZE = Window.size + SCREEN_RATIO = WINDOW_SIZE[0] / WINDOW_SIZE[1] + + +Window.bind(on_resize=change_window_size) +change_window_size() + + +def apply_boundaries(x, min_x, max_x): + if x > max_x: + return max_x + if x < min_x: + return min_x + return x + + +def norm(x, y): + return sqrt(pow(x, 2) + pow(y, 2)) + + +TRANSPARENT_WHITE = Color(1.0, 1.0, 1.0, .5) +HIGH_TRANSPARENT_WHITE = Color(1.0, 1.0, 1.0, .3) + +##################################### +### Classes for controls on mobil ### +##################################### + + +class MobileJoystick(): + def __init__(self, layout: RelativeLayout): + self.center_x = 0.15 + self.center_y = 0.2 + self.width = 0.12 + self.height = 0.12 + self.extension_factor = 2 + self.is_active = False + self.widget = Widget(pos_hint={"center_x": self.center_x, "center_y": self.center_y}, size_hint=( + self.width * self.extension_factor, self.height * self.extension_factor * SCREEN_RATIO)) + layout.add_widget(self.widget) + self.back_to_zero() + self.widget.bind(on_touch_move=self.on_touch_move) + self.widget.bind(on_touch_down=self.on_touch_down) + self.widget.bind(on_touch_up=self.on_touch_up) + self.draw() + + def on_touch_move(self, el, touch): + if touch.x < WINDOW_SIZE[0] // 2: + self.x = touch.x + self.y = touch.y + + def on_touch_down(self, el, touch): + if touch.x < WINDOW_SIZE[0] // 2: + self.is_active = True + + def on_touch_up(self, el, touch): + if touch.x < WINDOW_SIZE[0] // 2: + self.is_active = False + self.back_to_zero() + self.draw() + + def back_to_zero(self): + self.x = self.center_x * WINDOW_SIZE[0] + self.y = self.center_y * WINDOW_SIZE[1] + + def draw(self): + self.widget.pos_hint = { + "center_x": self.center_x, "center_y": self.center_y} + self.widget.size_hint = (self.width * self.extension_factor, + self.height * self.extension_factor * SCREEN_RATIO) + self.widget.canvas.clear() + # Réglage de la couleur + self.widget.canvas.add(TRANSPARENT_WHITE) + # Affichage du cercle extérieur + self.main_center_x = self.center_x * WINDOW_SIZE[0] + self.main_center_y = self.center_y * WINDOW_SIZE[1] + main_radius = self.width * WINDOW_SIZE[1] + self.norme_cercle = main_radius + self.widget.canvas.add( + Line(circle=(self.main_center_x, self.main_center_y, main_radius), width=5)) + # Affichage du cercle intérieur + max_x = self.main_center_x + main_radius + min_x = self.main_center_x - main_radius + self.in_center_x = apply_boundaries(self.x, min_x, max_x) + max_y = self.main_center_y + main_radius + min_y = self.main_center_y - main_radius + self.in_center_y = apply_boundaries(self.y, min_y, max_y) + in_radius = main_radius // 2 + self.widget.canvas.add( + Line(circle=(self.in_center_x, self.in_center_y, in_radius), width=3)) + + def get_direction(self): + if not (self.is_active): + return 0, 0 + else: + diff_x = apply_boundaries( + self.x - self.main_center_x, -self.norme_cercle, self.norme_cercle) + diff_y = apply_boundaries( + self.y - self.main_center_y, -self.norme_cercle, self.norme_cercle) + norme = norm(diff_x, diff_y) + if norme > 0: + return diff_x / norme, diff_y / norme + else: + return 0, 0 + + def recursive_update(self): + self.draw() + + +class MobileButton(): + def __init__(self, layout: RelativeLayout): + self.center_x = 0.85 + self.center_y = 0.2 + self.width = 0.12 + self.height = 0.12 + self.extension_factor = 2 + self.is_active = False + self.last_state = True + self.widget = Widget(pos_hint={"center_x": self.center_x, "center_y": self.center_y}, size_hint=( + self.width * self.extension_factor, self.height * self.extension_factor * SCREEN_RATIO)) + layout.add_widget(self.widget) + self.widget.bind(on_touch_move=self.on_touch_move) + self.widget.bind(on_touch_down=self.on_touch_down) + self.widget.bind(on_touch_up=self.on_touch_up) + self.draw() + + def get_state(self): + return self.is_active + + def on_touch_move(self, el, touch): + if touch.x > WINDOW_SIZE[0] // 2: + if self.widget.collide_point(touch.x, touch.y): + self.is_active = True + else: + self.is_active = False + + def on_touch_down(self, el, touch): + if touch.x > WINDOW_SIZE[0] // 2: + if self.widget.collide_point(touch.x, touch.y): + self.is_active = True + + def on_touch_up(self, el, touch): + if touch.x > WINDOW_SIZE[0] // 2: + if self.widget.collide_point(touch.x, touch.y): + self.is_active = False + + def draw(self): + self.widget.pos_hint = { + "center_x": self.center_x, "center_y": self.center_y} + self.widget.size_hint = (self.width * self.extension_factor, + self.height * self.extension_factor * SCREEN_RATIO) + if self.last_state != self.is_active: + self.widget.canvas.clear() + # Réglage de la couleur + if self.is_active == True: + self.widget.canvas.add(HIGH_TRANSPARENT_WHITE) + else: + self.widget.canvas.add(TRANSPARENT_WHITE) + # Affichage du cercle extérieur + self.main_center_x = self.center_x * WINDOW_SIZE[0] + self.main_center_y = self.center_y * WINDOW_SIZE[1] + main_radius = self.width * WINDOW_SIZE[1] + self.widget.canvas.add( + Line(circle=(self.main_center_x, self.main_center_y, main_radius), width=5)) + self.last_state = self.is_active + + def recursive_update(self): + self.draw() diff --git a/tools/tools_settings.py b/tools/tools_settings.py new file mode 100644 index 0000000..65fe60c --- /dev/null +++ b/tools/tools_settings.py @@ -0,0 +1,36 @@ +""" +Module for the settings of the applications. +""" + + +################# +### Constants ### +################# + + +from tools.tools_basis import ( + save_json_file +) +from tools.tools_constants import ( + SETTINGS, + PATH_SETTINGS +) + + +############### +### Classes ### +############### + + +class Achievements(): + def __init__(self) -> None: + self.endings = SETTINGS["endings"] + self.high_score = SETTINGS["high_score"] + + def update_high_score(self, score): + if self.high_score < score: + self.high_score = score + SETTINGS["high_score"] = self.high_score + save_json_file(PATH_SETTINGS, SETTINGS) + +my_achievements = Achievements() \ No newline at end of file diff --git a/tools/tools_sound.py b/tools/tools_sound.py new file mode 100644 index 0000000..2c8c370 --- /dev/null +++ b/tools/tools_sound.py @@ -0,0 +1,159 @@ +""" +Module to manage musics and sound effects +""" + +############### +### Imports ### +############### + +import os + +from math import exp + +from kivy.core.audio import SoundLoader + +from tools.tools_constants import ( + FPS, + SOUND_VOLUME, + MUSIC_VOLUME, + PATH_SOUNDS, + PATH_MUSICS +) + +############### +### Classes ### +############### + +class MusicMixer(): + """ + Classe destinée à gérer la musique dans le jeu + Une seule musique peut être jouée à la fois. + """ + + def __init__(self, dict_music): + self.musics = dict_music + + def change_volume(self, name, new_volume): + self.musics[name].volume = new_volume + + def play(self, name, loop=False, timecode=0, stop_other_sounds=True): + if stop_other_sounds: + self.stop() + self.musics[name].play() + if timecode != 0: + # Ne marche pas + self.musics[name].seek(1) + self.musics[name].loop = loop + + def stop(self): + for key in self.musics: + if self.musics[key].state == "play": + self.musics[key].stop() + +class DynamicMusicMixer(MusicMixer): + """ + Classe destinée à jouer des bruitages sur lesquels on peut appliquer des effets en jeu. + """ + + def __init__(self, dict_music): + super().__init__(dict_music) + self.instructions = [] + dico_frame_state = {} + for key in dict_music: + dico_frame_state[key] = 0 + self.dico_frame_state = dico_frame_state + + def fade_out(self, name, duration, mode="linear"): + if mode == "exp": + self.instructions.append(("exp_fade_out", name, duration)) + else: + self.instructions.append(("fade_out", name, duration)) + + def recursive_update(self): + pop_list = [] + # Parcours des instructions à effectuer + for instruction in self.instructions: + new_volume = None + # Calcul de l'instruction + if instruction[0] == "fade_out": + key, duration = instruction[1], instruction[2] + volume = self.musics[key].volume + frame_to_fade = FPS * duration + fade_diff = SOUND_VOLUME / frame_to_fade + new_volume = volume - fade_diff + elif instruction[0] == "exp_fade_out": + key, duration = instruction[1], instruction[2] + frame_to_fade = FPS * duration + t = 60 * self.dico_frame_state[key] / frame_to_fade + self.dico_frame_state[key] += 1 + new_volume = exp_fade_out(t) * SOUND_VOLUME + # Application du changement de volume + if new_volume is not None: + if new_volume > 0: + self.musics[key].volume = new_volume + else: + self.musics[key].volume = 0 + self.musics[key].stop() + self.musics[key].volume = SOUND_VOLUME + self.dico_frame_state[key] = 0 + pop_list.append(instruction) + # Enlève les instructions terminées + for el in pop_list: + self.instructions.remove(el) + +class SoundMixer(): + """ + Classe destinée à gérer les bruitages dans le jeu + Elle est capable de jouer plusieurs fois le même son en même temps + """ + + def __init__(self, dict_sound, volume, sound_limit=10): + self.sounds = {} + self.sound_limit = sound_limit + for key in dict_sound: + self.sounds[key] = [SoundLoader.load(dict_sound[key]) + for i in range(sound_limit)] + for i in range(sound_limit): + self.sounds[key][i].volume = volume + + def play(self, name): + i = 0 + while i < self.sound_limit and self.sounds[name][i].state == "play": + i += 1 + if i < self.sound_limit: + self.sounds[name][i].play() + else: + print("Unable to play the desired sound, channel saturation") + +################# +### Functions ### +################# + +def exp_fade_out(t): + return 1 - exp((t - 60) * 0.15) + + +def load_sounds(folder, volume): + """Fonction pour charger tous les sons d'un coup. + Prend en entrée un dictionnaire avec le nom de chaque son et sa position. + Renvoie un dictionnaire avec le nom de chaque son et le son lui-même. + """ + dico = {} + for file in os.listdir(folder): + name_file = file.split(".")[0] + dico[name_file] = SoundLoader.load(folder + file) + dico[name_file].volume = volume + return dico + +############### +### Process ### +############### + + +# Load the dictionnaries +MUSIC_DICT = load_sounds(PATH_MUSICS, MUSIC_VOLUME) +SOUND_DICT = load_sounds(PATH_SOUNDS, SOUND_VOLUME) + +# Create the mixer +music_mixer = DynamicMusicMixer(MUSIC_DICT) +sound_mixer = DynamicMusicMixer(SOUND_DICT)