def __init__(self, master, car_id: int, **kwargs):
        self.__height = 150
        Image.__init__(self,
                       RESOURCES.IMG["garage_cars"][car_id],
                       height=self.__height)
        Clickable.__init__(self, master, **kwargs, highlight_thickness=0)

        self.__id = car_id
        self.__all_max_speed = list()
        self.__all_acceleration = list()
        self.__all_maniablities = list()
        self.__all_braking = list()
        for infos in CAR_INFOS.values():
            self.__all_max_speed.append(infos["max_speed"])
            self.__all_acceleration.append(infos["acceleration"])
            self.__all_maniablities.append(infos["maniability"])
            self.__all_braking.append(infos["braking"])

        self.take_focus(True)
        self.disable_key_joy()
        self.disable_mouse()
        self.master.bind_event(pygame.KEYDOWN, self.on_click_down)
        self.master.bind_event(pygame.KEYUP, self.on_click_up)
        self.master.bind_event(pygame.JOYHATMOTION, self.on_click_down)
        self.master.bind_event(pygame.JOYHATMOTION, self.on_click_up)
Esempio n. 2
0
 def set_box_hit(self, box: Box, hit: bool) -> None:
     box.state = Button.DISABLED
     hit = bool(hit)
     img = {False: "hatch", True: "cross"}[hit]
     image = Image(RESOURCES.IMG[img], size=box.size)
     image.center = box.center
     self.box_hit_img.add(image)
Esempio n. 3
0
 def __init__(self, name: str, boxes: Sequence[tuple[int, int]], orient: str):
     Image.__init__(self, RESOURCES.IMG[name])
     self.name = name
     self.__orient = Ship.VERTICAL
     self.ship_size = len(boxes)
     self.boxes_pos = [tuple(box_pos) for box_pos in boxes]
     self.set_height(self.ship_size * BOX_SIZE[0])
     self.orient = orient
     self.__boxes_covered = list()
 def __init__(self, master, assembly_file: str, *args, **kwargs):
     TextButton.__init__(self, master, *args, **kwargs, command=self.select)
     self.file = assembly_file
     self.master = master
     self.comment = get_champion_comment(assembly_file)
     self.selected = False
     self.select_img = Image(IMG["valide"])
     self.select_img.hide()
     master.add(self.select_img)
Esempio n. 5
0
 def __init__(self, player_id: int):
     Window.__init__(self,
                     bg_color=(0, 200, 255),
                     bg_music=RESOURCES.MUSIC["setup"])
     self.gameplay = Gameplay(player_id)
     self.count_down = CountDown(self,
                                 60,
                                 "Time left: {seconds}",
                                 font=(None, 70),
                                 color=WHITE)
     self.start_count_down = lambda: self.count_down.start(
         at_end=self.timeout) if self.client_socket.connected() else None
     params_for_all_buttons = {
         "bg": GREEN,
         "hover_bg": GREEN_LIGHT,
         "active_bg": GREEN_DARK,
         "highlight_color": YELLOW
     }
     self.button_back = ImageButton(self,
                                    RESOURCES.IMG["arrow_blue"],
                                    **params_for_all_buttons,
                                    rotate=180,
                                    size=50,
                                    callback=self.stop)
     self.navy_grid = DrawableListVertical(offset=0, bg_color=(0, 157, 255))
     for i in range(NB_LINES_BOXES):
         box_line = DrawableListHorizontal(offset=0)
         for j in range(NB_COLUMNS_BOXES):
             box_line.add(BoxSetup(self, size=BOX_SIZE, pos=(i, j)))
         self.navy_grid.add(box_line)
     self.ships_list = DrawableListVertical(offset=70, justify="left")
     for ship_name, ship_infos in SHIPS.items():
         ship_line = DrawableListHorizontal(offset=ship_infos["offset"])
         for _ in range(ship_infos["nb"]):
             ship_line.add(ShipSetup(self, ship_name, ship_infos["size"]))
         self.ships_list.add(ship_line)
     option_size = 50
     self.button_restart = Button.withImageOnly(
         self,
         Image(RESOURCES.IMG["reload_blue"], size=option_size),
         callback=self.reinit_all_ships,
         **params_for_all_buttons)
     self.button_random = Button.withImageOnly(self,
                                               Image(
                                                   RESOURCES.IMG["random"],
                                                   size=option_size),
                                               callback=self.shuffle,
                                               **params_for_all_buttons)
     self.button_play = Button(self,
                               "Play",
                               font=(None, 40),
                               callback=self.play,
                               **params_for_all_buttons)
Esempio n. 6
0
    def __init__(self, master: Window):
        Window.__init__(self, bg_color=BACKGROUND_COLOR)
        self.bind_key(pygame.K_ESCAPE, lambda event: self.stop())

        self.master = master
        self.logo = Image(RESOURCES.IMG["logo"])
        arrow = pygame.transform.flip(RESOURCES.IMG["arrow"], True, False)
        self.button_back = ImageButton(self,
                                       img=arrow,
                                       width=100,
                                       callback=self.stop,
                                       active_offset=(0, 5),
                                       highlight_color=YELLOW)
        self.grid = FourInARowGrid(self, self.width / 2, self.height * 0.75)
        self.__player_turn = 0
        self.player_who_start_first = 0
        self.player = 0
        self.__turn = str()
        self.__turn_dict = dict()
        self.__score_player = self.__score_enemy = 0
        self.__highlight_line_window_callback = None
        self.enemy = str()

        self.ai = FourInARowAI()

        self.text_score = Text()
        self.text_player_turn = Text()
        self.left_options = ButtonListVertical(offset=50)
        self.left_options.add(
            Button(self, "Restart", theme="option", callback=self.restart),
            Button(self, "Quit game", theme="option", callback=self.quit_game))
        self.text_winner = Text()
        self.text_drawn_match = Text("Drawn match.")

        self.token_players = Grid(self)
        for row in range(2):
            self.token_players.place(CircleShape(20,
                                                 PLAYER_TOKEN_COLOR[row + 1],
                                                 outline=2,
                                                 outline_color=WHITE),
                                     row,
                                     column=1,
                                     padx=5,
                                     pady=5,
                                     justify="left")

        self.enemy_quit_dialog = EnemyQuitGame(self)
Esempio n. 7
0
    def __init__(self):
        MainWindow.__init__(self, title=f"Navy - v{__version__}", size=(1280, 720), resources=RESOURCES, config=WINDOW_CONFIG_FILE)

        self.bg = Image(RESOURCES.IMG["menu_bg"], size=self.size)
        self.logo = Image(RESOURCES.IMG["logo"])

        Button.set_default_theme("default")
        Button.set_theme("default", {
            "bg": GREEN,
            "hover_bg": GREEN_LIGHT,
            "active_bg": GREEN_DARK,
            "highlight_color": YELLOW,
            "outline": 3,
        })
        Button.set_theme("title", {
            "font": (None, 100),
        })
        Button.set_theme("option", {
            "font": ("calibri", 30),
        })
        Scale.set_default_theme("default")
        Scale.set_theme("default", {
            "color": TRANSPARENT,
            "scale_color": GREEN,
            "highlight_color": YELLOW,
            "outline": 3
        })

        params_for_dialogs = {
            "outline": 5,
            "hide_all_without": [self.bg, self.logo]
        }

        self.start_game = NavySetup()
        self.multiplayer_server = PlayerServer(self, **params_for_dialogs)
        self.multiplayer_client = PlayerClient(self, **params_for_dialogs)
        self.dialog_credits = Credits(self, **params_for_dialogs)

        self.menu_buttons = ButtonListVertical(offset=30)
        self.menu_buttons.add(
            Button(self, "Play against AI", theme="title", callback=lambda: self.start_game.start(1)),
            Button(self, "Play as P1", theme="title", callback=self.multiplayer_server.mainloop),
            Button(self, "Play as P2", theme="title", callback=self.multiplayer_client.mainloop),
            Button(self, "Quit", theme="title", callback=self.stop)
        )

        self.button_credits = Button(self, "Credits", font=("calibri", 50), callback=self.dialog_credits.mainloop)
 def __init__(self, master, ship_name: str, ship_size: int):
     Image.__init__(self, RESOURCES.IMG[ship_name])
     self.name = ship_name
     self.__orient = ShipSetup.VERTICAL
     self.master = master
     self.ship_size = ship_size
     self.orient = ShipSetup.HORIZONTAL
     self.set_width(self.ship_size * BOX_SIZE[0])
     self.clicked = False
     self.on_move = False
     self.mouse_offset = (0, 0)
     self.default_center = (0, 0)
     self.center_before_click = (0, 0)
     self.__boxes_covered = list()
     self.master.bind_event(pygame.MOUSEBUTTONDOWN, self.select_event)
     self.master.bind_event(pygame.MOUSEBUTTONUP, self.unselect_event)
     self.master.bind_event(pygame.MOUSEMOTION, self.move_event)
 def __init__(self):
     Window.__init__(self, bg_color=(0, 200, 255))
     self.gameplay = Gameplay()
     self.enemy_quit_window = EnemyQuitGame(self)
     self.transition = GameSetupTransition()
     self.count_down = CountDown(self,
                                 60,
                                 "Time left: {seconds}",
                                 font=(None, 70),
                                 color=WHITE)
     self.start_count_down = lambda: self.count_down.start(
         at_end=self.timeout) if self.client_socket.connected() else None
     self.button_back = ImageButton(self,
                                    RESOURCES.IMG["arrow_blue"],
                                    rotate=180,
                                    size=50,
                                    callback=self.stop)
     self.navy_grid = Grid(self, bg_color=(0, 157, 255))
     self.__boxes_dict = {(i, j): BoxSetup(self, size=BOX_SIZE, pos=(i, j))
                          for i in range(NB_LINES_BOXES)
                          for j in range(NB_COLUMNS_BOXES)}
     self.__boxes_list = list(self.__boxes_dict.values())
     self.navy_grid.place_multiple(self.__boxes_dict)
     self.ships_list = DrawableListVertical(offset=70, justify="left")
     for ship_name, ship_infos in SHIPS.items():
         ship_line = DrawableListHorizontal(offset=ship_infos["offset"])
         for _ in range(ship_infos["nb"]):
             ship_line.add(ShipSetup(self, ship_name, ship_infos["size"]))
         self.ships_list.add(ship_line)
     option_size = 50
     self.button_restart = Button(self,
                                  img=Image(RESOURCES.IMG["reload_blue"],
                                            size=option_size),
                                  callback=self.reinit_all_ships)
     self.button_random = Button(self,
                                 img=Image(RESOURCES.IMG["random"],
                                           size=option_size),
                                 callback=self.shuffle)
     self.button_play = Button(self,
                               "Play",
                               font=(None, 40),
                               callback=self.play)
Esempio n. 10
0
    def __init__(self):
        Window.__init__(self, size=(1280, 720), flags=pygame.DOUBLEBUF, bg_music=RESOURCES.MUSIC["menu"])
        self.set_icon(RESOURCES.IMG["icon"])
        self.set_title(f"Navy - v{__version__}")
        self.set_fps(60)
        self.disable_key_joy_focus_for_all_window()

        self.bg = Image(RESOURCES.IMG["menu_bg"], self.size)
        self.logo = Image(RESOURCES.IMG["logo"])

        params_for_all_buttons = {
            "font": (None, 100),
            "bg": GREEN,
            "hover_bg": GREEN_LIGHT,
            "active_bg": GREEN_DARK,
            "outline": 3,
            "highlight_color": YELLOW,
        }

        params_for_dialogs = {
            "outline": 5,
            "hide_all_without": [self.bg, self.logo]
        }

        self.start_game = NavySetup(1)
        self.multiplayer_server = PlayerServer(self, **params_for_dialogs)
        self.multiplayer_client = PlayerClient(self, **params_for_dialogs)
        self.dialog_credits = Credits(self, **params_for_dialogs)
        self.dialog_options = Options(self, **params_for_dialogs)

        self.menu_buttons = ButtonListVertical(offset=30)
        self.menu_buttons.add(
            Button(self, "Play against AI", **params_for_all_buttons, callback=self.start_game.mainloop),
            Button(self, "Play as P1", **params_for_all_buttons, callback=self.multiplayer_server.mainloop),
            Button(self, "Play as P2", **params_for_all_buttons, callback=self.multiplayer_client.mainloop),
            Button(self, "Quit", **params_for_all_buttons, callback=self.stop)
        )

        self.button_credits = Button(self, "Credits", callback=self.dialog_credits.mainloop, **params_for_all_buttons)
        self.button_settings = Button.withImageOnly(self, Image(RESOURCES.IMG["settings"], size=self.button_credits.height - 20), callback=self.dialog_options.mainloop, **params_for_all_buttons)
    def __init__(self, master):
        Window.__init__(self,
                        bg_color=master.bg_color,
                        bg_music=master.bg_music)
        params_for_button = {
            "highlight_color": YELLOW,
            "hover_sound": RESOURCES.SFX["select"],
            "on_click_sound": RESOURCES.SFX["back"]
        }
        self.master = master
        self.button_back = ImageButton(self,
                                       img=RESOURCES.IMG["blue_arrow"],
                                       **params_for_button,
                                       callback=self.stop)

        self.objects.add(master.text_highscore, master.text_money)

        self.text_title = Text("ENVIRONMENT", (RESOURCES.FONT["algerian"], 90),
                               GREEN_DARK,
                               shadow=True,
                               shadow_x=2,
                               shadow_y=2)
        self.environment = ButtonListHorizontal(offset=15)
        self.texts = DrawableList()
        for name, color in ENVIRONMENT.items():
            b = Button(self,
                       img=Image(RESOURCES.IMG[name],
                                 max_width=180,
                                 max_height=180),
                       compound="center",
                       outline=3,
                       callback=lambda env=name: self.play(env),
                       bg=color,
                       hover_bg=change_saturation(color, -20),
                       active_bg=change_brightness(color, -20),
                       hover_sound=RESOURCES.SFX["select"],
                       on_click_sound=RESOURCES.SFX["validate"],
                       highlight_color=YELLOW)
            b.set_size(200)
            self.texts.add(
                Text(name.upper(), (RESOURCES.FONT["algerian"], 50),
                     GREEN_DARK,
                     shadow=True,
                     shadow_x=2,
                     shadow_y=2))
            self.environment.add(b)

        self.bind_key(pygame.K_ESCAPE,
                      lambda event: self.stop(sound=RESOURCES.SFX["back"]))
        self.bind_joystick(
            0, "B", lambda event: self.stop(sound=RESOURCES.SFX["back"]))
Esempio n. 12
0
class ChampionViewer(TextButton):
    def __init__(self, master, assembly_file: str, *args, **kwargs):
        TextButton.__init__(self, master, *args, **kwargs, command=self.select)
        self.file = assembly_file
        self.master = master
        self.comment = get_champion_comment(assembly_file)
        self.selected = False
        self.select_img = Image(IMG["valide"])
        self.select_img.hide()
        master.add(self.select_img)

    def hide_all(self):
        self.hide()
        self.select_img.hide()

    def update(self, *args, **kwargs):
        self.select_img.move(left=self.right + 10, centery=self.centery)

    def select(self):
        self.selected = not self.selected
        if self.selected:
            if len(self.master.selected) >= MAX_NB_PLAYERS:
                self.selected = False
                return
            self.select_img.show()
            self.master.selected.append(self)
        else:
            self.select_img.hide()
            try:
                self.master.selected.remove(self)
            except ValueError:
                pass

    def update_file(self, new_file: str, new_name: str):
        self.file = new_file
        self.set_text(new_name)
        self.comment = get_champion_comment(new_file)
Esempio n. 13
0
class FourInARowGameplay(Window):
    def __init__(self, master: Window):
        Window.__init__(self, bg_color=BACKGROUND_COLOR)
        self.bind_key(pygame.K_ESCAPE, lambda event: self.stop())

        self.master = master
        self.logo = Image(RESOURCES.IMG["logo"])
        arrow = pygame.transform.flip(RESOURCES.IMG["arrow"], True, False)
        self.button_back = ImageButton(self,
                                       img=arrow,
                                       width=100,
                                       callback=self.stop,
                                       active_offset=(0, 5),
                                       highlight_color=YELLOW)
        self.grid = FourInARowGrid(self, self.width / 2, self.height * 0.75)
        self.__player_turn = 0
        self.player_who_start_first = 0
        self.player = 0
        self.__turn = str()
        self.__turn_dict = dict()
        self.__score_player = self.__score_enemy = 0
        self.__highlight_line_window_callback = None
        self.enemy = str()

        self.ai = FourInARowAI()

        self.text_score = Text()
        self.text_player_turn = Text()
        self.left_options = ButtonListVertical(offset=50)
        self.left_options.add(
            Button(self, "Restart", theme="option", callback=self.restart),
            Button(self, "Quit game", theme="option", callback=self.quit_game))
        self.text_winner = Text()
        self.text_drawn_match = Text("Drawn match.")

        self.token_players = Grid(self)
        for row in range(2):
            self.token_players.place(CircleShape(20,
                                                 PLAYER_TOKEN_COLOR[row + 1],
                                                 outline=2,
                                                 outline_color=WHITE),
                                     row,
                                     column=1,
                                     padx=5,
                                     pady=5,
                                     justify="left")

        self.enemy_quit_dialog = EnemyQuitGame(self)

    def start(self,
              enemy: str,
              player=1,
              player_name=None,
              enemy_name=None,
              ai_level=None) -> None:
        self.player = player
        self.enemy = enemy
        if enemy == LAN_PLAYER:
            player_name = str(player_name)
            self.client_socket.send("name", player_name)
            result = self.client_socket.wait_for("name")
            if result == self.client_socket.QUIT_MESSAGE:
                return
            enemy_name = str(self.client_socket.get(result))
            self.__turn_dict = {
                1: {
                    1: player_name,
                    2: enemy_name
                }[player],
                2: {
                    1: enemy_name,
                    2: player_name
                }[player]
            }
        else:
            player_name = str(player_name) if player_name is not None else "P1"
            enemy_name = str(enemy_name) if enemy_name is not None else "P2"
            self.__turn_dict = {
                1: "You" if self.enemy == AI else player_name,
                2: "AI" if self.enemy == AI else enemy_name
            }
            if self.enemy == AI:
                self.ai.level = ai_level
        for row, name in enumerate(self.__turn_dict.values()):
            self.token_players.place(Text(name + ":"),
                                     row,
                                     column=0,
                                     padx=5,
                                     pady=5,
                                     justify="right")
        self.mainloop()

    def on_start_loop(self) -> None:
        self.grid[0].focus_set()
        self.score_player = self.score_enemy = 0
        self.player_who_start_first = 0
        self.text_winner.hide()
        self.restart(init=True)

    def on_quit(self) -> None:
        self.stop_connection()

    def quit_game(self) -> None:
        self.stop()
        self.master.stop()

    def restart(self, init=False) -> None:
        if not init:
            self.client_socket.send("restart")
        self.grid.reset()
        self.remove_window_callback(self.__highlight_line_window_callback)
        if self.player_who_start_first == 0:
            if self.enemy == AI:
                self.player_turn = 1
            elif self.enemy == LOCAL_PLAYER or (self.enemy == LAN_PLAYER
                                                and self.player == 1):
                self.player_turn = random.randint(1, 2)
                if self.enemy == LAN_PLAYER:
                    self.client_socket.send("player_turn",
                                            int(self.player_turn))
            elif self.enemy == LAN_PLAYER and self.player == 2:
                result = self.client_socket.wait_for("player_turn")
                if result == self.client_socket.QUIT_MESSAGE:
                    self.stop()
                    return
                self.player_turn = self.client_socket.get(result)
        elif self.text_winner.is_shown():
            self.player_turn = (self.player_who_start_first % 2) + 1
        else:
            self.player_turn = self.player_who_start_first
        self.player_who_start_first = self.player_turn
        self.text_winner.hide()
        self.text_drawn_match.hide()

    def place_objects(self) -> None:
        self.logo.move(centerx=self.centerx, top=10)
        self.grid.midbottom = self.midbottom
        self.text_score.move(left=10, top=self.grid.top)
        self.text_player_turn.move(left=self.text_score.left,
                                   top=self.text_score.bottom + 50)
        self.left_options.move(centerx=(self.left + self.grid.left) // 2,
                               top=self.text_player_turn.bottom + 50)
        self.text_winner.center = ((self.grid.right + self.right) // 2,
                                   self.grid.centery)
        self.token_players.move(centerx=self.text_winner.centerx,
                                top=self.grid.top)
        self.text_drawn_match.center = self.text_winner.center

    def set_grid(self) -> None:
        self.grid.set_obj_on_side(on_top=self.button_back,
                                  on_left=self.left_options[0])
        self.button_back.set_obj_on_side(on_bottom=self.left_options[0],
                                         on_right=self.grid[0])
        self.left_options.set_obj_on_side(on_top=self.button_back,
                                          on_right=self.grid[0])

    @property
    def player_turn(self) -> int:
        return self.__player_turn

    @player_turn.setter
    def player_turn(self, player: int) -> None:
        self.__player_turn = player
        self.__turn = turn = self.__turn_dict[player]
        self.text_player_turn.message = f"Player turn:\n{turn}"
        self.place_objects()
        if self.enemy != LOCAL_PLAYER:
            for column in filter(lambda column: not column.full(),
                                 self.grid.columns):
                column.set_enabled(self.player_turn == self.player)
            if self.enemy == AI and self.player_turn == 2:
                self.after(500, self.play, self.ai.play(self.grid.map))

    @property
    def score_player(self) -> int:
        return self.__score_player

    @score_player.setter
    def score_player(self, value: int) -> None:
        self.__score_player = value
        self.__update_text_score()

    @property
    def score_enemy(self) -> int:
        return self.__score_enemy

    @score_enemy.setter
    def score_enemy(self, value: int) -> None:
        self.__score_enemy = value
        self.__update_text_score()

    def __update_text_score(self) -> None:
        self.text_score.message = "Score:\n{you}: {score_1}\n{enemy}: {score_2}".format(
            you=self.__turn_dict[1],
            enemy=self.__turn_dict[2],
            score_1=self.score_player,
            score_2=self.score_enemy)
        self.place_objects()

    def update(self) -> None:
        if self.enemy == LAN_PLAYER:
            if self.client_socket.recv("column"):
                self.play(self.client_socket.get("column"))
            if self.client_socket.recv("restart"):
                self.restart()
            if self.client_socket.recv(self.client_socket.QUIT_MESSAGE):
                self.enemy_quit_dialog.text.message = "{}\nhas left the game".format(
                    self.__turn_dict[(self.player % 2) + 1])
                self.enemy_quit_dialog.mainloop()

    def play(self, column: int) -> None:
        self.block_only_event([
            pygame.KEYDOWN, pygame.KEYUP, pygame.JOYBUTTONDOWN,
            pygame.JOYBUTTONUP, pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP
        ])
        if self.enemy == LAN_PLAYER and self.player_turn == self.player:
            self.client_socket.send("column", column)
        self.grid.play(self.player_turn, column)
        line = self.check_victory()
        if line:
            for c in self.grid.columns:
                c.disable()
            self.left_options[0].focus_set()
            self.update_winner()
            self.highlight_line(line)
        elif self.grid.full():
            self.left_options[0].focus_set()
            self.text_drawn_match.show()
        else:
            self.player_turn = (self.player_turn % 2) + 1
            if self.enemy == LOCAL_PLAYER:
                self.draw_and_refresh()
                pygame.time.wait(500)
        self.clear_all_events()
        self.allow_all_events()

    def check_victory(self) -> list[tuple[int, int]]:
        grid = self.grid.map
        grid_pos_getter = [
            lambda row, col, index: (row, col + index),  # Check row (->)
            lambda row, col, index: (row, col - index),  # Check row (<-)
            lambda row, col, index: (row + index, col),  # Check column
            lambda row, col, index:
            (row + index, col - index),  # Check diagonal (/)
            lambda row, col, index:
            (row + index, col + index),  # Check diagonal (\)
        ]
        all_box_pos = list()
        box_pos = list()
        for row, column in filter(lambda pos: grid[pos] != 0, grid):
            for grid_pos in grid_pos_getter:
                index = 0
                box_pos.clear()
                while grid.get(grid_pos(row, column, index),
                               -1) == grid[row, column]:
                    box_pos.append(grid_pos(row, column, index))
                    index += 1
                if len(box_pos) >= 4:
                    all_box_pos.extend(
                        filter(lambda pos: pos not in all_box_pos, box_pos))
        return all_box_pos

    def highlight_line(self, line: list[tuple[int, int]], highlight=True):
        for row, col in line:
            box = self.grid.columns[col].boxes[row]
            if highlight:
                box.circle.color = GREEN
            else:
                box.value = box.value
        self.__highlight_line_window_callback = self.after(
            500, self.highlight_line, line=line, highlight=not highlight)

    def update_winner(self):
        if self.player_turn == 1:
            self.score_player += 1
        else:
            self.score_enemy += 1
        winner = self.__turn
        self.text_winner.message = f"Winner:\n{winner}"
        self.text_winner.show()
class Gameplay(Window):
    def __init__(self, car_id: int, env: str):
        Window.__init__(self,
                        bg_color=ENVIRONMENT[env],
                        bg_music=RESOURCES.MUSIC["gameplay"])
        self.bind_key(pygame.K_ESCAPE, lambda event: self.pause())
        self.bind_joystick(0, "START", lambda event: self.pause())

        font = RESOURCES.FONT["cooperblack"]

        # Demaraction lines
        self.road = DrawableListVertical(bg_color=GRAY, offset=70)
        self.white_bands = list()
        white_bands_width = 50  #px
        white_lines_height = 10  #px
        for i in range(5):
            if i % 2 == 0:
                self.road.add(
                    RectangleShape(self.width, white_lines_height, WHITE))
            else:
                white_bands = DrawableListHorizontal(offset=20)
                while white_bands.width < self.width:
                    white_bands.add(
                        RectangleShape(white_bands_width, white_lines_height,
                                       WHITE))
                self.road.add(white_bands)
                self.white_bands.append(white_bands)

        # Environment
        self.env_top = DrawableListHorizontal(offset=400)
        self.env_bottom = DrawableListHorizontal(offset=400)
        while self.env_top.width < self.width:
            self.env_top.add(Image(RESOURCES.IMG[env], height=110))
        while self.env_bottom.width < self.width:
            self.env_bottom.add(Image(RESOURCES.IMG[env], height=110))

        #Infos
        params_for_infos = {
            "font": (font, 45),
            "color": YELLOW,
            "shadow": True,
            "shadow_x": 3,
            "shadow_y": 3
        }
        self.infos_score = Info("Score", round_n=0, **params_for_infos)
        self.infos_speed = Info("Speed",
                                extension="km/h",
                                **params_for_infos,
                                justify="right")
        self.infos_distance = Info("Distance",
                                   round_n=2,
                                   extension="km",
                                   **params_for_infos,
                                   justify="right")
        self.infos_time_100 = Info("High speed", **params_for_infos)
        self.infos_time_opposite = Info("Opposite side", **params_for_infos)
        self.clock_time_100 = Clock()
        self.clock_time_opposite = Clock()
        self.total_time_100 = self.total_time_opposite = 0

        self.car = PlayerCar(car_id)
        self.speed = 0
        self.traffic = TrafficCarList(nb_ways=4, max_nb_car=6)
        self.clock_traffic = Clock()
        self.img_crash = Image(RESOURCES.IMG["crash"], size=150)
        self.count_down = CountDown(self,
                                    3,
                                    font=(font, 90),
                                    color=YELLOW,
                                    shadow=True,
                                    shadow_x=5,
                                    shadow_y=5)
        self.last_car_way = 0

        # Background
        self.background = DrawableList(draw=False)
        self.background.add(self.env_top, self.env_bottom, *self.white_bands,
                            self.traffic)

        # Default values
        self.update_clock = Clock()
        self.update_time = 15  #ms
        self.pixel_per_sec = 6  # For 1km/h
        self.pixel_per_ms = self.pixel_per_sec * self.update_time / 1000
        self.paused = False
        self.go_to_garage = self.restart = False
        self.crashed_car = None

        self.disable_key_joy_focus()
        self.init_game()

    def pause(self):
        if not self.count_down.is_shown():
            self.paused = True
            self.car.stop_animation()
            for car in self.traffic:
                car.stop_animation()
        pause = Pause(self)
        pause.mainloop()
        if self.loop and not self.count_down.is_shown():
            self.count_down.start(at_end=self.return_to_game)
            self.objects.set_priority(self.count_down, self.objects.end)

    def return_to_game(self):
        self.paused = False
        self.car.restart_animation()
        for car in self.traffic:
            car.restart_animation()
        self.clock_traffic.tick()
        self.clock_time_100.tick()
        self.clock_time_opposite.tick()
        self.update_clock.tick()

    def init_game(self):
        self.go_to_garage = False
        self.paused = False
        self.crashed_car = None
        for info in (self.infos_speed, self.infos_score, self.infos_distance,
                     self.infos_time_100, self.infos_time_opposite):
            info.value = 0
            if info in (self.infos_time_100, self.infos_time_opposite):
                info.hide()
        self.total_time_100 = self.total_time_opposite = 0
        self.count_down.start()
        self.img_crash.hide()

    def place_objects(self):
        self.count_down.center = self.road.center = self.center
        self.infos_score.move(topleft=(10, 10))
        self.infos_speed.move(right=self.right - 10, top=10)
        self.infos_distance.move(right=self.right - 10,
                                 bottom=self.road.top - 10)
        self.infos_time_100.move(left=10, bottom=self.road.top - 10)
        self.infos_time_opposite.move(left=10, top=self.road.bottom + 10)
        self.car.move(centery=self.road.centery, left=50)
        self.env_top.move(centerx=self.centerx,
                          centery=(self.top + self.road[0].top) / 2)
        self.env_bottom.move(centerx=self.centerx,
                             centery=(self.road[-1].bottom + self.bottom) / 2)

    def update(self):
        if self.paused:
            return
        self.update_player_car()
        if self.update_clock.elapsed_time(self.update_time):
            self.update_infos()
            self.update_background()
            self.update_traffic()
            self.rect_to_update = (self.road.rect, self.env_top.rect,
                                   self.env_bottom.rect, self.infos_score.rect,
                                   self.infos_speed.rect,
                                   self.infos_distance.rect,
                                   self.infos_time_100.rect,
                                   self.infos_time_opposite.rect)

    def update_player_car(self):
        if not self.count_down.is_shown():
            joystick = self.joystick[0]
            controls = SAVE["controls"]
            car_handling = (
                (controls["speed_up"], self.car.speed_up),
                (controls["brake"], self.car.brake),
                (controls["up"], self.car.moveUp),
                (controls["down"], self.car.moveDown),
            )
            for control, car_function in car_handling:
                car_function(self.keyboard.is_pressed(control["key"]))
                car_function(joystick.get_value(control["joy"]))
            if SAVE["auto_acceleration"] is True:
                self.car.speed_up()
        self.car.update(self.pixel_per_ms)
        if self.car.top < self.road[0].bottom + 5:
            self.car.top = self.road[0].bottom + 5
        elif self.car.bottom > self.road[-1].top - 5:
            self.car.bottom = self.road[-1].top - 5
        if not self.car.is_crashed():
            self.speed = self.car.speed
            for car in self.traffic:
                collision = pygame.sprite.collide_mask(self.car, car)
                if collision:
                    self.car.crash(car)
                    self.crashed_car = car
                    self.play_sound(RESOURCES.SFX["crash"])
                    self.img_crash.show()
                    self.img_crash.move(centerx=collision[0] + self.car.left,
                                        centery=collision[1] + self.car.top)
                    self.objects.set_priority(self.img_crash, self.objects.end)
        elif self.car.right <= 0 and self.crashed_car.right <= 0:
            self.end_game()

    def car_in_opposite_side(self) -> bool:
        return bool(self.car.bottom < self.road.centery)

    def update_infos(self):
        min_speed = 30
        score_to_add = (self.car.speed - min_speed) / 5
        bonus = False
        if self.car.speed > 30 and self.car_in_opposite_side():
            self.infos_time_opposite.show()
            self.infos_time_opposite.value = self.clock_time_opposite.get_elapsed_time(
            ) / 1000
            score_to_add += 120
            bonus = True
        else:
            self.total_time_opposite += self.infos_time_opposite.value
            self.infos_time_opposite.value = 0
            self.clock_time_opposite.restart()
            self.infos_time_opposite.hide()
        if self.car.speed >= 100:
            self.infos_time_100.show()
            self.infos_time_100.value = self.clock_time_100.get_elapsed_time(
            ) / 1000
            score_to_add += 150
            bonus = True
        else:
            self.total_time_100 += self.infos_time_100.value
            self.infos_time_100.value = 0
            self.clock_time_100.restart()
            self.infos_time_100.hide()
        if bonus:
            self.infos_score.color = GREEN_DARK
            self.infos_score.shadow_color = YELLOW
        else:
            self.infos_score.color = YELLOW
            self.infos_score.shadow_color = BLACK
        self.infos_score.value += score_to_add * self.update_time / 1000
        self.infos_speed.value = self.car.speed
        self.infos_distance.value += self.car.speed * self.pixel_per_ms / (
            1000 * 3.6)

    def update_background(self):
        offset = self.speed * self.pixel_per_ms
        self.background.move_ip(-offset, 0)
        for white_bands_list in self.white_bands:
            if white_bands_list[0].right <= 0:
                white_bands_list.remove_from_index(0)
            if white_bands_list[-1].right < self.right:
                white_bands_list.add(
                    RectangleShape(*white_bands_list[-1].size, WHITE))
        for env in (self.env_top, self.env_bottom):
            img = env[0]
            if img.right <= 0:
                img.move(left=env[-1].right + env.offset)
                env.set_priority(img, env.end)
        if self.img_crash.is_shown():
            self.img_crash.move_ip(-offset, 0)

    def update_traffic(self):
        for car in self.traffic.drawable:
            car.update(self.pixel_per_ms)
            if car.right < 0:
                self.traffic.remove(car)
        for way, car_list in enumerate(self.traffic.ways, 1):
            for i in range(1, len(car_list)):
                car_1 = car_list[i - 1]
                car_2 = car_list[i]
                if car_2.left - car_1.right < 20:
                    if (way in [1, 2]) and (car_1.speed < car_2.speed):
                        car_2.speed = car_1.speed
                    elif (way in [3, 4]) and (car_1.speed > car_2.speed):
                        car_1.speed = car_2.speed
        ratio = (2 - (round(self.infos_score.value) / 20000)) * 1000
        if self.car.speed > 30 and self.clock_traffic.elapsed_time(ratio):
            if self.traffic.empty(
            ) or self.traffic.last.right < self.right - 20:
                self.traffic.add_cars(self, self.road,
                                      round(self.infos_score.value))

    def end_game(self):
        for car in self.traffic:
            car.stop_animation()
        self.crashed_car = None
        score = round(self.infos_score.value)
        distance = round(self.infos_distance.value, 1)
        time_100 = round(self.total_time_100, 1)
        time_opposite = round(self.total_time_opposite, 1)
        window = EndGame(self, score, distance, time_100, time_opposite)
        window.mainloop()
        if self.restart:
            self.traffic.clear()
            self.car.move(left=50, centery=self.road.centery)
            self.car.restart()
            self.init_game()
    def __init__(self):
        Window.__init__(self,
                        bg_color=GRAY,
                        bg_music=RESOURCES.MUSIC["garage"])
        params_for_all_buttons = {
            "bg": GREEN,
            "hover_bg": GREEN_LIGHT,
            "active_bg": GREEN_DARK,
            "highlight_color": YELLOW,
            "hover_sound": RESOURCES.SFX["select"],
        }
        params_for_button_except_back = {
            "on_click_sound": RESOURCES.SFX["validate"],
            "disabled_sound": RESOURCES.SFX["block"],
            "disabled_bg": GRAY_LIGHT,
        }
        params_for_button_except_back.update(params_for_all_buttons)
        params_for_car_viewer = {
            k: params_for_button_except_back[k]
            for k in ["hover_sound", "on_click_sound"]
        }
        self.button_back = ImageButton(self,
                                       RESOURCES.IMG["blue_arrow"],
                                       **params_for_all_buttons,
                                       on_click_sound=RESOURCES.SFX["back"],
                                       callback=self.stop)
        self.car_viewer = CarViewer(self, SAVE["car"], **params_for_car_viewer)

        size_progress_bar = (300, 30)
        self.speed_bar = ProgressBar(*size_progress_bar, TRANSPARENT, GREEN)
        self.maniability_bar = ProgressBar(*size_progress_bar, TRANSPARENT,
                                           GREEN)
        self.braking_bar = ProgressBar(*size_progress_bar, TRANSPARENT, GREEN)

        self.left_arrow = ImageButton(
            self,
            img=RESOURCES.IMG["left_arrow"],
            active_img=RESOURCES.IMG["left_arrow_hover"],
            **params_for_button_except_back,
            callback=self.car_viewer.decrease_id)
        self.right_arrow = ImageButton(
            self,
            img=RESOURCES.IMG["right_arrow"],
            active_img=RESOURCES.IMG["right_arrow_hover"],
            **params_for_button_except_back,
            callback=self.car_viewer.increase_id)
        for arrow in [self.left_arrow, self.right_arrow]:
            arrow.take_focus(False)
        self.button_price = Button(self,
                                   font=(RESOURCES.FONT["algerian"], 40),
                                   img=Image(RESOURCES.IMG["piece"], size=40),
                                   compound="right",
                                   callback=self.buy_car,
                                   **params_for_button_except_back)
        self.button_play = Button(self,
                                  "Play",
                                  font=(RESOURCES.FONT["algerian"], 70),
                                  callback=self.play,
                                  **params_for_button_except_back)
        self.text_money = Text(format_number(SAVE["money"]),
                               (RESOURCES.FONT["algerian"], 50),
                               YELLOW,
                               img=Image(RESOURCES.IMG["piece"], height=40),
                               compound="right")
        self.text_highscore = Text(
            "Highscore: {}".format(format_number(SAVE["highscore"])),
            (RESOURCES.FONT["algerian"], 50), YELLOW)
        self.padlock = Image(RESOURCES.IMG["padlock"])
        self.bind_key(pygame.K_ESCAPE,
                      lambda event: self.stop(sound=RESOURCES.SFX["back"]))
        self.bind_joystick(
            0, "B", lambda event: self.stop(sound=RESOURCES.SFX["back"]))
class Garage(Window):
    def __init__(self):
        Window.__init__(self,
                        bg_color=GRAY,
                        bg_music=RESOURCES.MUSIC["garage"])
        params_for_all_buttons = {
            "bg": GREEN,
            "hover_bg": GREEN_LIGHT,
            "active_bg": GREEN_DARK,
            "highlight_color": YELLOW,
            "hover_sound": RESOURCES.SFX["select"],
        }
        params_for_button_except_back = {
            "on_click_sound": RESOURCES.SFX["validate"],
            "disabled_sound": RESOURCES.SFX["block"],
            "disabled_bg": GRAY_LIGHT,
        }
        params_for_button_except_back.update(params_for_all_buttons)
        params_for_car_viewer = {
            k: params_for_button_except_back[k]
            for k in ["hover_sound", "on_click_sound"]
        }
        self.button_back = ImageButton(self,
                                       RESOURCES.IMG["blue_arrow"],
                                       **params_for_all_buttons,
                                       on_click_sound=RESOURCES.SFX["back"],
                                       callback=self.stop)
        self.car_viewer = CarViewer(self, SAVE["car"], **params_for_car_viewer)

        size_progress_bar = (300, 30)
        self.speed_bar = ProgressBar(*size_progress_bar, TRANSPARENT, GREEN)
        self.maniability_bar = ProgressBar(*size_progress_bar, TRANSPARENT,
                                           GREEN)
        self.braking_bar = ProgressBar(*size_progress_bar, TRANSPARENT, GREEN)

        self.left_arrow = ImageButton(
            self,
            img=RESOURCES.IMG["left_arrow"],
            active_img=RESOURCES.IMG["left_arrow_hover"],
            **params_for_button_except_back,
            callback=self.car_viewer.decrease_id)
        self.right_arrow = ImageButton(
            self,
            img=RESOURCES.IMG["right_arrow"],
            active_img=RESOURCES.IMG["right_arrow_hover"],
            **params_for_button_except_back,
            callback=self.car_viewer.increase_id)
        for arrow in [self.left_arrow, self.right_arrow]:
            arrow.take_focus(False)
        self.button_price = Button(self,
                                   font=(RESOURCES.FONT["algerian"], 40),
                                   img=Image(RESOURCES.IMG["piece"], size=40),
                                   compound="right",
                                   callback=self.buy_car,
                                   **params_for_button_except_back)
        self.button_play = Button(self,
                                  "Play",
                                  font=(RESOURCES.FONT["algerian"], 70),
                                  callback=self.play,
                                  **params_for_button_except_back)
        self.text_money = Text(format_number(SAVE["money"]),
                               (RESOURCES.FONT["algerian"], 50),
                               YELLOW,
                               img=Image(RESOURCES.IMG["piece"], height=40),
                               compound="right")
        self.text_highscore = Text(
            "Highscore: {}".format(format_number(SAVE["highscore"])),
            (RESOURCES.FONT["algerian"], 50), YELLOW)
        self.padlock = Image(RESOURCES.IMG["padlock"])
        self.bind_key(pygame.K_ESCAPE,
                      lambda event: self.stop(sound=RESOURCES.SFX["back"]))
        self.bind_joystick(
            0, "B", lambda event: self.stop(sound=RESOURCES.SFX["back"]))

    def update(self):
        self.left_arrow.set_visibility(self.car_viewer.id > 1)
        self.right_arrow.set_visibility(
            self.car_viewer.id < len(self.car_viewer))
        if not SAVE["owned_cars"][self.car_viewer.id]:
            self.padlock.show()
            price = self.car_viewer["price"]
            if isinstance(price, int):
                self.button_price.show()
                self.button_price.text = format_number(price)
                self.button_price.state = Button.NORMAL if SAVE[
                    "money"] >= price else Button.DISABLED
            else:
                self.button_price.hide()
            self.button_play.state = Button.DISABLED
        else:
            self.padlock.hide()
            self.button_price.hide()
            self.button_play.state = Button.NORMAL
            SAVE["car"] = self.car_viewer.id
        max_s = self.car_viewer.max_speed
        min_a = self.car_viewer.min_acceleration
        max_m = self.car_viewer.max_maniability
        min_b = self.car_viewer.min_braking
        s = self.car_viewer["max_speed"]
        a = self.car_viewer["acceleration"]
        m = self.car_viewer["maniability"]
        b = self.car_viewer["braking"]
        self.speed_bar.percent = (s + min_a) / (max_s + a)
        self.maniability_bar.percent = m / max_m
        self.braking_bar.percent = min_b / b

    def place_objects(self):
        self.button_back.topleft = (5, 5)
        self.car_viewer.move(center=self.center)
        self.padlock.center = self.car_viewer.center

        self.braking_bar.move(bottom=self.car_viewer.top - 40,
                              centerx=self.car_viewer.centerx + 100)
        self.maniability_bar.move(bottom=self.braking_bar.top - 10,
                                  centerx=self.car_viewer.centerx + 100)
        self.speed_bar.move(bottom=self.maniability_bar.top - 10,
                            centerx=self.car_viewer.centerx + 100)

        self.speed_bar.show_label("Speed/Acc.",
                                  ProgressBar.S_LEFT,
                                  font=(RESOURCES.FONT["algerian"], 40))
        self.maniability_bar.show_label("Maniability",
                                        ProgressBar.S_LEFT,
                                        font=(RESOURCES.FONT["algerian"], 40))
        self.braking_bar.show_label("Braking",
                                    ProgressBar.S_LEFT,
                                    font=(RESOURCES.FONT["algerian"], 40))

        self.left_arrow.move(left=self.left + 50, centery=self.centery)
        self.right_arrow.move(right=self.right - 50, centery=self.centery)
        self.button_price.move(centerx=self.centerx,
                               top=self.car_viewer.bottom + 25)
        self.button_play.move(bottom=self.bottom - 50, right=self.right - 10)

        self.text_money.move(top=5, right=self.right - 10)
        self.text_highscore.move(bottom=self.bottom - 50, left=5)

    def set_grid(self):
        self.button_back.set_obj_on_side(on_bottom=self.car_viewer)
        self.car_viewer.set_obj_on_side(on_top=self.button_back,
                                        on_bottom=self.button_price)
        self.button_price.set_obj_on_side(on_top=self.car_viewer,
                                          on_bottom=self.button_play)
        self.button_play.set_obj_on_side(on_top=self.button_price)

    def buy_car(self):
        confirm_window = ConfirmPayement(self)
        confirm_window.mainloop()
        if confirm_window.buyed:
            SAVE["money"] -= self.car_viewer["price"]
            SAVE["owned_cars"][self.car_viewer.id] = True
            self.text_money.message = format_number(SAVE["money"])
            if Clickable.MODE != Clickable.MODE_MOUSE:
                self.car_viewer.focus_set()

    def play(self):
        environment_chooser = EnvironmentChooser(self)
        environment_chooser.mainloop()
        self.text_money.message = format_number(SAVE["money"])
        self.text_highscore.message = "Highscore: {}".format(
            format_number(SAVE["highscore"]))
    def __init__(self, car_id: int, env: str):
        Window.__init__(self,
                        bg_color=ENVIRONMENT[env],
                        bg_music=RESOURCES.MUSIC["gameplay"])
        self.bind_key(pygame.K_ESCAPE, lambda event: self.pause())
        self.bind_joystick(0, "START", lambda event: self.pause())

        font = RESOURCES.FONT["cooperblack"]

        # Demaraction lines
        self.road = DrawableListVertical(bg_color=GRAY, offset=70)
        self.white_bands = list()
        white_bands_width = 50  #px
        white_lines_height = 10  #px
        for i in range(5):
            if i % 2 == 0:
                self.road.add(
                    RectangleShape(self.width, white_lines_height, WHITE))
            else:
                white_bands = DrawableListHorizontal(offset=20)
                while white_bands.width < self.width:
                    white_bands.add(
                        RectangleShape(white_bands_width, white_lines_height,
                                       WHITE))
                self.road.add(white_bands)
                self.white_bands.append(white_bands)

        # Environment
        self.env_top = DrawableListHorizontal(offset=400)
        self.env_bottom = DrawableListHorizontal(offset=400)
        while self.env_top.width < self.width:
            self.env_top.add(Image(RESOURCES.IMG[env], height=110))
        while self.env_bottom.width < self.width:
            self.env_bottom.add(Image(RESOURCES.IMG[env], height=110))

        #Infos
        params_for_infos = {
            "font": (font, 45),
            "color": YELLOW,
            "shadow": True,
            "shadow_x": 3,
            "shadow_y": 3
        }
        self.infos_score = Info("Score", round_n=0, **params_for_infos)
        self.infos_speed = Info("Speed",
                                extension="km/h",
                                **params_for_infos,
                                justify="right")
        self.infos_distance = Info("Distance",
                                   round_n=2,
                                   extension="km",
                                   **params_for_infos,
                                   justify="right")
        self.infos_time_100 = Info("High speed", **params_for_infos)
        self.infos_time_opposite = Info("Opposite side", **params_for_infos)
        self.clock_time_100 = Clock()
        self.clock_time_opposite = Clock()
        self.total_time_100 = self.total_time_opposite = 0

        self.car = PlayerCar(car_id)
        self.speed = 0
        self.traffic = TrafficCarList(nb_ways=4, max_nb_car=6)
        self.clock_traffic = Clock()
        self.img_crash = Image(RESOURCES.IMG["crash"], size=150)
        self.count_down = CountDown(self,
                                    3,
                                    font=(font, 90),
                                    color=YELLOW,
                                    shadow=True,
                                    shadow_x=5,
                                    shadow_y=5)
        self.last_car_way = 0

        # Background
        self.background = DrawableList(draw=False)
        self.background.add(self.env_top, self.env_bottom, *self.white_bands,
                            self.traffic)

        # Default values
        self.update_clock = Clock()
        self.update_time = 15  #ms
        self.pixel_per_sec = 6  # For 1km/h
        self.pixel_per_ms = self.pixel_per_sec * self.update_time / 1000
        self.paused = False
        self.go_to_garage = self.restart = False
        self.crashed_car = None

        self.disable_key_joy_focus()
        self.init_game()
    def __init__(self, master, score: int, distance: float, time_100: float,
                 time_opposite: float):
        Window.__init__(self, master=master, bg_music=master.bg_music)
        self.master = master
        self.bg = RectangleShape(*self.size, (0, 0, 0, 170))
        self.text_score = Text(f"Your score\n{score}",
                               (RESOURCES.FONT["algerian"], 90),
                               YELLOW,
                               justify="center")
        self.img_highscore = Image(RESOURCES.IMG["new_high_score"], width=150)
        if score > SAVE["highscore"]:
            SAVE["highscore"] = score
        else:
            self.img_highscore.hide()

        MAX_MONEY = pow(10, 9) - 1
        money_distance = round(300.40 * distance)
        money_time_100 = round(12.5 * time_100)
        money_time_opposite = round(21.7 * time_opposite)
        money_gained = money_distance + money_time_100 + money_time_opposite
        total = SAVE["money"] + money_gained
        SAVE["money"] = MAX_MONEY if total > MAX_MONEY else total
        self.text_money = Text(format_number(SAVE["money"]),
                               (RESOURCES.FONT["algerian"], 50),
                               YELLOW,
                               img=Image(RESOURCES.IMG["piece"], height=40),
                               compound="right")

        font = ("calibri", 50)
        self.frame = RectangleShape(0.75 * self.width,
                                    0.45 * self.height,
                                    BLACK,
                                    outline=1,
                                    outline_color=WHITE)
        self.text_distance = Text(f"Distance: {distance}", font, WHITE)
        self.img_green_arrow_distance = Image(RESOURCES.IMG["green_arrow"],
                                              height=40)
        self.text_money_distance = Text(money_distance,
                                        font,
                                        WHITE,
                                        img=Image(RESOURCES.IMG["piece"],
                                                  height=40),
                                        compound="right")
        self.text_time_100 = Text(f"Time up to 100: {time_100}", font, WHITE)
        self.img_green_arrow_time_100 = Image(RESOURCES.IMG["green_arrow"],
                                              height=40)
        self.text_money_time_100 = Text(money_time_100,
                                        font,
                                        WHITE,
                                        img=Image(RESOURCES.IMG["piece"],
                                                  height=40),
                                        compound="right")
        self.text_time_opposite = Text(
            f"Time in opposite side: {time_opposite}", font, WHITE)
        self.img_green_arrow_time_opposite = Image(
            RESOURCES.IMG["green_arrow"], height=40)
        self.text_money_time_opposite = Text(money_time_opposite,
                                             font,
                                             WHITE,
                                             img=Image(RESOURCES.IMG["piece"],
                                                       height=40),
                                             compound="right")
        self.total_money = DrawableListHorizontal(offset=10)
        self.total_money.add(
            Text("TOTAL: ", font, WHITE),
            Text(money_gained,
                 font,
                 WHITE,
                 img=Image(RESOURCES.IMG["piece"], height=40),
                 compound="right"))

        params_for_all_buttons = {
            "font": (RESOURCES.FONT["algerian"], 50),
            "bg": GREEN,
            "hover_bg": GREEN_LIGHT,
            "active_bg": GREEN_DARK,
            "hover_sound": RESOURCES.SFX["select"],
            "on_click_sound": RESOURCES.SFX["validate"],
            "outline": 3,
            "highlight_color": YELLOW
        }
        self.menu_buttons = ButtonListHorizontal(offset=30)
        self.menu_buttons.add(
            Button(self,
                   "Restart",
                   **params_for_all_buttons,
                   callback=self.restart_game),
            Button(self,
                   "Garage",
                   **params_for_all_buttons,
                   callback=self.return_to_garage),
            Button(self,
                   "Menu",
                   **params_for_all_buttons,
                   callback=self.return_to_menu))
class PyGameCase(MainWindow):

    RUNNING_STATE_SUFFIX = " - Running"

    def __init__(self):
        super().__init__(title=f"Py-Game-Case - v{__version__}", size=(1280, 720), flags=pygame.RESIZABLE, bg_color=(0, 0, 100), resources=RESOURCES)

        TitleButton.set_default_theme("default")
        TitleButton.set_theme("default", {
            "font": ("calibri", 30),
            "bg": TRANSPARENT,
            "fg": WHITE,
            "outline": 0,
            "hover_bg": set_color_alpha(WHITE, 100),
            "active_bg": set_color_alpha(WHITE, 50),
            "highlight_color": WHITE,
            "highlight_thickness": 3,
            "x_size": self.w * 0.25,
            "y_add_size": 30,
            "justify": ("left", "center"),
            "offset": (10, 0),
            "hover_offset": (0, -5),
            "active_offset": (0, 10)
        })
        SettingsButton.set_default_theme("default")
        SettingsButton.set_theme("default", {
            "bg": WHITE,
            "hover_bg": YELLOW,
            "active_bg": change_brightness(YELLOW, -75),
            "outline": 7,
            "outline_color": WHITE,
            "highlight_thickness": 0
        })

        self.launcher_updater = Updater(__version__)

        self.image_game_preview = SpriteDict()
        self.bg = DrawableListHorizontal()
        left_color = BLACK
        right_color = set_color_alpha(BLACK, 60)
        self.bg.add(
            RectangleShape(self.w * 0.25, self.h, left_color),
            HorizontalGradientShape(self.w * 0.5, self.h, left_color, right_color),
            RectangleShape(self.w * 0.25, self.h, right_color),
        )
        self.logo = Image(RESOURCES.IMG["logo"], width=self.bg[0].width)

        self.buttons_game_launch = ButtonListVertical(offset=30)
        self.buttons_game_dict = dict[str, TitleButton]()
        self.window_game_dict = dict[str, MainWindow]()
        for game_id, game_infos in GAMES.items():
            button = TitleButton(
                self, lambda game_id=game_id: self.show_preview(game_id), text=game_infos["name"],
                callback=lambda game_id=game_id: self.launch_game(game_id)
            )
            self.image_game_preview[game_id] = Sprite(RESOURCES.IMG[game_id], height=self.height)
            self.buttons_game_dict[game_id] = button
        self.buttons_game_launch.add_multiple(self.buttons_game_dict.values())
        self.game_id = None
        self.game_launched_processes = GameProcessList()

        self.settings_section = SettingsWindow(self)
        self.updater_window = UpdaterWindow(self, self.launcher_updater)
        self.side_board = SideBoard(self)
        self.side_board.add_option("Settings", self.settings_section.mainloop)
        if psutil.WINDOWS:
            self.side_board.add_option("Update", lambda: self.updater_window.start(install=True))

        self.button_settings = SettingsButton(self, size=40, callback=self.side_board.mainloop)
        self.button_settings.force_use_highlight_thickness(True)

        self.transition = GameLaunchTransition()

    def place_objects(self) -> None:
        self.bg.center = self.center
        self.logo.move(left=10, top=10)
        self.buttons_game_launch.move(left=10, top=self.logo.bottom + 50)
        self.image_game_preview.midright = self.midleft
        self.button_settings.move(right=self.right - 20, top=20)

    def set_grid(self) -> None:
        self.button_settings.set_obj_on_side(on_bottom=self.buttons_game_launch[0], on_left=self.buttons_game_launch[0])
        self.buttons_game_launch.set_obj_on_side(on_top=self.button_settings, on_right=self.button_settings)

    def on_start_loop(self):
        self.image_game_preview.move(right=self.left, top=self.top)
        save_objects_center = list()
        for obj in [self.logo, *self.buttons_game_dict.values(), self.button_settings]:
            save_objects_center.append((obj, obj.get_move()))
        self.buttons_game_launch.right = self.left
        self.button_settings.left = self.right
        default_logo_width = self.logo.width
        self.logo.load(RESOURCES.IMG["logo"])
        self.logo.midtop = self.midbottom
        if psutil.WINDOWS:
            if self.launcher_updater.has_a_downloaded_update() or (SETTINGS.auto_check_update and self.launcher_updater.has_a_new_release()):
                self.logo.animate_move(self, speed=20, top=0, centerx=self.centerx)
                self.updater_window.start()
        self.logo.animate_move(self, speed=20, midbottom=self.center)
        loading = ProgressBar(
            default_logo_width, 40, TRANSPARENT, GREEN,
            from_=0, to=len(GAMES), default=len(self.window_game_dict),
            outline_color=WHITE, border_radius=10
        )
        loading.center = self.logo.center
        loading.show_percent(ProgressBar.S_INSIDE, font=("calibri", 30), color=WHITE)
        self.objects.add(loading)
        self.objects.set_priority(loading, 0, relative_to=self.logo)
        loading.animate_move(self, speed=10, centerx=loading.centerx, top=self.logo.bottom + 20)
        try:
            for game_id, game_infos in filter(lambda item: item[0] not in self.window_game_dict, GAMES.items()):
                with ThemeNamespace(game_id):
                    self.window_game_dict[game_id] = game_infos["window"]()
                    loading.value = len(self.window_game_dict)
                    self.draw_and_refresh(pump=True)
        except:
            sys.excepthook(*sys.exc_info())
            self.stop(force=True)
        pygame.time.wait(100)
        loading.animate_move(self, speed=10, center=self.logo.center)
        self.objects.remove(loading)
        self.logo.animation.rotate(angle=360, offset=5, point="center").scale_width(width=default_logo_width, offset=7).start(self)
        for obj, move in save_objects_center:
            obj.animate_move(self, speed=20, **move)
        self.focus_mode(Button.MODE_KEY)
        pygame.event.clear()

    def on_quit(self) -> None:
        self.launcher_updater.close()

    def update(self) -> None:
        if self.game_launched_processes:
            for process in self.game_launched_processes.check_for_terminated_games():
                self.buttons_game_dict[process.game_id].text = GAMES[process.game_id]["name"]
                self.buttons_game_dict[process.game_id].state = Button.NORMAL
            # if not self.game_launched_processes and not pygame.display.get_active():
            #     pass
        if all(not button.has_focus() and not button.hover for button in self.buttons_game_launch) and self.game_id is not None:
            self.image_game_preview.animate_move_in_background(self, speed=75, right=self.left)
            self.game_id = None

    def show_preview(self, game_id: str) -> None:
        if self.game_id == game_id:
            return
        self.game_id = game_id
        self.image_game_preview.animate_move_in_background(self, speed=75, right=self.left, after_animation=self.__show_preview)

    def __show_preview(self) -> None:
        self.image_game_preview.use_sprite(self.game_id)
        self.image_game_preview.animate_move_in_background(self, speed=75, right=self.right)

    def launch_game(self, game_id: str) -> None:
        if not SETTINGS.launch_in_same_window:
            self.game_launched_processes.launch(game_id)
            self.buttons_game_dict[game_id].text += PyGameCase.RUNNING_STATE_SUFFIX
            self.buttons_game_dict[game_id].state = Button.DISABLED
            self.iconify()
        else:
            self.game_id = game_id
            if self.image_game_preview.get_actual_sprite_name() != game_id:
                self.image_game_preview.animate_move(self, speed=75, right=self.left)
            self.image_game_preview.use_sprite(game_id)
            self.image_game_preview.animate_move(self, speed=75, right=self.right)
            window = self.window_game_dict[game_id]
            window.mainloop(transition=self.transition)

    def close(self) -> None:
        if self.game_launched_processes:
            self.iconify()
        else:
            self.stop(force=True)
    def __init__(self):
        super().__init__(title=f"Py-Game-Case - v{__version__}", size=(1280, 720), flags=pygame.RESIZABLE, bg_color=(0, 0, 100), resources=RESOURCES)

        TitleButton.set_default_theme("default")
        TitleButton.set_theme("default", {
            "font": ("calibri", 30),
            "bg": TRANSPARENT,
            "fg": WHITE,
            "outline": 0,
            "hover_bg": set_color_alpha(WHITE, 100),
            "active_bg": set_color_alpha(WHITE, 50),
            "highlight_color": WHITE,
            "highlight_thickness": 3,
            "x_size": self.w * 0.25,
            "y_add_size": 30,
            "justify": ("left", "center"),
            "offset": (10, 0),
            "hover_offset": (0, -5),
            "active_offset": (0, 10)
        })
        SettingsButton.set_default_theme("default")
        SettingsButton.set_theme("default", {
            "bg": WHITE,
            "hover_bg": YELLOW,
            "active_bg": change_brightness(YELLOW, -75),
            "outline": 7,
            "outline_color": WHITE,
            "highlight_thickness": 0
        })

        self.launcher_updater = Updater(__version__)

        self.image_game_preview = SpriteDict()
        self.bg = DrawableListHorizontal()
        left_color = BLACK
        right_color = set_color_alpha(BLACK, 60)
        self.bg.add(
            RectangleShape(self.w * 0.25, self.h, left_color),
            HorizontalGradientShape(self.w * 0.5, self.h, left_color, right_color),
            RectangleShape(self.w * 0.25, self.h, right_color),
        )
        self.logo = Image(RESOURCES.IMG["logo"], width=self.bg[0].width)

        self.buttons_game_launch = ButtonListVertical(offset=30)
        self.buttons_game_dict = dict[str, TitleButton]()
        self.window_game_dict = dict[str, MainWindow]()
        for game_id, game_infos in GAMES.items():
            button = TitleButton(
                self, lambda game_id=game_id: self.show_preview(game_id), text=game_infos["name"],
                callback=lambda game_id=game_id: self.launch_game(game_id)
            )
            self.image_game_preview[game_id] = Sprite(RESOURCES.IMG[game_id], height=self.height)
            self.buttons_game_dict[game_id] = button
        self.buttons_game_launch.add_multiple(self.buttons_game_dict.values())
        self.game_id = None
        self.game_launched_processes = GameProcessList()

        self.settings_section = SettingsWindow(self)
        self.updater_window = UpdaterWindow(self, self.launcher_updater)
        self.side_board = SideBoard(self)
        self.side_board.add_option("Settings", self.settings_section.mainloop)
        if psutil.WINDOWS:
            self.side_board.add_option("Update", lambda: self.updater_window.start(install=True))

        self.button_settings = SettingsButton(self, size=40, callback=self.side_board.mainloop)
        self.button_settings.force_use_highlight_thickness(True)

        self.transition = GameLaunchTransition()
    def __init__(self):
        MainWindow.__init__(self,
                            title=f"4 in a row - v{__version__}",
                            size=(1280, 720),
                            bg_color=BACKGROUND_COLOR,
                            resources=RESOURCES,
                            config=WINDOW_CONFIG_FILE)
        self.logo = Image(RESOURCES.IMG["logo"])

        Text.set_default_theme("default")
        Text.set_theme(
            "default", {
                "font": RESOURCES.font("heavy", 45),
                "color": YELLOW,
                "shadow": True,
                "shadow_x": 3,
                "shadow_y": 3
            })
        Text.set_theme("form", {
            "font": RESOURCES.font("heavy", 35),
        })
        Button.set_default_theme("default")
        Button.set_theme(
            "default", {
                "fg": YELLOW,
                "disabled_fg": WHITE,
                "shadow": True,
                "shadow_x": 3,
                "shadow_y": 3,
                "bg": BLUE,
                "hover_bg": (0, 175, 255),
                "active_bg": BLUE,
                "outline": 0,
                "highlight_color": WHITE,
                "highlight_thickness": 1,
                "border_bottom_left_radius": 45,
                "border_top_right_radius": 45,
                "x_add_size": 150,
                "offset": (0, -5),
                "hover_offset": (-10, 0),
                "active_offset": (0, 10),
            })
        Button.set_theme("title", {
            "font": RESOURCES.font("heavy", 70),
            "y_add_size": -50
        })
        Button.set_theme("option", {
            "font": RESOURCES.font("heavy", 40),
            "y_add_size": -20
        })
        Button.set_theme("section", {
            "bg": BLUE_DARK,
            "active_bg": BLUE_DARK,
            "disabled_bg": GRAY_LIGHT
        })
        Entry.set_default_theme("default")
        Entry.set_theme(
            "default", {
                "width": 12,
                "font": RESOURCES.font("afterglow", 25),
                "highlight_color": BLACK,
                "highlight_thickness": 3
            })

        gameplay = FourInARowGameplay(self)
        ai_level_selector = AILevelSelectorSection(self, gameplay)
        local_playing = LocalPlayingSection(self, gameplay)
        lan_playing_server = LANPlayingP1(self, gameplay)
        lan_playing_client = LANPlayingP2(self, gameplay)
        self.buttons = ButtonListVertical(offset=80, justify="right")
        self.buttons.add(
            Button(self,
                   "Play against AI",
                   theme="title",
                   callback=ai_level_selector.mainloop),
            Button(self,
                   "Multiplayer",
                   theme="title",
                   callback=local_playing.mainloop),
            Button(self,
                   "Play as P1 (LAN)",
                   theme="title",
                   callback=lan_playing_server.mainloop),
            Button(self,
                   "Play as P2 (LAN)",
                   theme="title",
                   callback=lan_playing_client.mainloop),
            Button(self, "Quit", theme="title", callback=self.stop),
        )
    def __init__(self, master: Window):
        Window.__init__(self, master=master, bg_music=master.bg_music)
        self.frame = RectangleShape(0.60 * self.width,
                                    0.60 * self.height,
                                    GREEN,
                                    outline=3)
        self.title = Text("Options", font=(RESOURCES.FONT["algerian"], 70))

        self.options_font = (RESOURCES.FONT["algerian"], 40)
        self.case_font = (RESOURCES.FONT["algerian"], 30)
        self.control_font = ("calibri", 20)
        params_for_all_scales = {
            "width": 0.45 * self.frame.w,
            "color": TRANSPARENT,
            "scale_color": GREEN_DARK,
            "from_": 0,
            "to": 100,
            "outline": 3,
        }
        params_for_all_buttons = {
            "highlight_color": YELLOW,
            "hover_sound": RESOURCES.SFX["select"],
            "disabled_sound": RESOURCES.SFX["block"]
        }
        params_for_option_buttons = {
            "on_click_sound": RESOURCES.SFX["validate"]
        }
        params_for_buttons = {
            "bg": GRAY_DARK,
            "fg": WHITE,
            "hover_bg": GRAY,
            "active_bg": BLACK
        }
        params_for_reset_button = {
            "bg": RED,
            "fg": WHITE,
            "hover_bg": RED_LIGHT,
            "active_bg": RED_DARK,
        }

        self.button_back = ImageButton(self,
                                       img=RESOURCES.IMG["blue_arrow"],
                                       on_click_sound=RESOURCES.SFX["back"],
                                       callback=self.stop,
                                       **params_for_all_buttons)
        self.button_change_page = Button(self,
                                         ">>",
                                         font=self.case_font,
                                         callback=self.change_page,
                                         **params_for_all_buttons,
                                         **params_for_option_buttons,
                                         **params_for_buttons)
        self.nb_pages = 2
        self.page = 1

        ## PAGE 1 ##
        valid_img = Image(RESOURCES.IMG["green_valid"])
        self.text_music = Text("Music:", self.options_font)
        self.cb_music = CheckBox(self,
                                 30,
                                 30,
                                 TRANSPARENT,
                                 image=valid_img,
                                 value=self.get_music_state(),
                                 callback=self.set_music_state,
                                 **params_for_all_buttons,
                                 **params_for_option_buttons)
        self.scale_music = Scale(
            self,
            **params_for_all_scales,
            **params_for_all_buttons,
            height=self.cb_music.height,
            default=Window.music_volume() * 100,
            callback=lambda value, percent: Window.set_music_volume(percent))
        self.text_sound = Text("SFX:", self.options_font)
        self.cb_sound = CheckBox(self,
                                 30,
                                 30,
                                 TRANSPARENT,
                                 image=valid_img,
                                 value=self.get_sound_state(),
                                 callback=self.set_sound_state,
                                 **params_for_all_buttons,
                                 **params_for_option_buttons)
        self.scale_sound = Scale(
            self,
            **params_for_all_scales,
            **params_for_all_buttons,
            height=self.cb_sound.height,
            default=Window.sound_volume() * 100,
            callback=lambda value, percent: Window.set_sound_volume(percent))
        self.text_fps = Text("FPS:", self.options_font)
        self.cb_show_fps = CheckBox(self,
                                    30,
                                    30,
                                    TRANSPARENT,
                                    image=valid_img,
                                    value=Window.fps_is_shown(),
                                    callback=self.show_fps,
                                    **params_for_all_buttons,
                                    **params_for_option_buttons)
        self.button_reset = Button(self,
                                   "Reset Save",
                                   font=(RESOURCES.FONT["algerian"], 30),
                                   callback=SAVE.reset,
                                   state=Button.DISABLED,
                                   **params_for_all_buttons,
                                   **params_for_option_buttons,
                                   **params_for_reset_button)
        ## PAGE 2 ##
        self.text_acceleration = Text("Accélérer:", self.options_font)
        self.button_auto_acceleration = Button(
            self,
            font=self.case_font,
            callback=lambda: SAVE.update(auto_acceleration=not SAVE[
                "auto_acceleration"]),
            **params_for_all_buttons,
            **params_for_option_buttons,
            **params_for_buttons)
        self.button_acceleration = Button(
            self,
            font=self.control_font,
            callback=lambda: self.choose_key("speed_up"),
            **params_for_all_buttons,
            **params_for_option_buttons,
            **params_for_buttons)
        self.text_brake = Text("Freiner:", self.options_font)
        self.button_brake = Button(self,
                                   font=self.control_font,
                                   callback=lambda: self.choose_key("brake"),
                                   **params_for_all_buttons,
                                   **params_for_option_buttons,
                                   **params_for_buttons)
        self.text_move_up = Text("Aller en haut:", self.options_font)
        self.button_move_up = Button(self,
                                     font=self.control_font,
                                     callback=lambda: self.choose_key("up"),
                                     **params_for_all_buttons,
                                     **params_for_option_buttons,
                                     **params_for_buttons)
        self.text_move_down = Text("Aller en bas:", self.options_font)
        self.button_move_down = Button(
            self,
            font=self.control_font,
            callback=lambda: self.choose_key("down"),
            **params_for_all_buttons,
            **params_for_option_buttons,
            **params_for_buttons)

        self.bind_key(pygame.K_ESCAPE,
                      lambda event: self.stop(sound=RESOURCES.SFX["back"]))
        self.bind_joystick(
            0, "B", lambda event: self.stop(sound=RESOURCES.SFX["back"]))