class PlayerClient(Dialog): def __init__(self, master, **kwargs): Dialog.__init__(self, master=master, bg_color=GREEN_DARK, **kwargs) params_for_all_buttons = { "font": ("calibri", 30), "bg": GREEN, "hover_bg": GREEN_LIGHT, "active_bg": GREEN_DARK, "highlight_color": YELLOW, "outline": 3 } self.start_game = NavySetup(2) self.text_title = Text("Connect to Player 1", ("calibri", 50)) self.ip = Entry(self, width=15, font=("calibri", 40), bg=GREEN, highlight_color=YELLOW, outline=2) self.text_ip_address = Text("IP address", ("calibri", 40), YELLOW) self.port = Entry(self, width=15, font=("calibri", 40), bg=GREEN, highlight_color=YELLOW, outline=2) self.text_port_of_connection = Text("Port", ("calibri", 40), YELLOW) self.text_connection = Text(font=("calibri", 25), color=YELLOW) self.text_connection.hide() self.button_connect = Button(self, "Connection", callback=self.connection, **params_for_all_buttons) self.button_cancel = Button(self, "Return to menu", callback=self.stop, **params_for_all_buttons) self.lets_play_countdown = CountDown(self, 3, "Connected.\nGame start in {seconds} seconds", font=("calibri", 35), color=YELLOW, justify="center") def on_dialog_quit(self): self.disable_text_input() self.stop_connection() def place_objects(self): self.frame.move(center=self.center) self.text_title.move(centerx=self.frame.centerx, top=self.frame.top + 50) self.lets_play_countdown.move(center=self.text_title.center) self.ip.move(centerx=self.frame.centerx + self.frame.w // 10, bottom=self.frame.centery - 10) self.text_ip_address.move(centery=self.ip.centery, right=self.ip.left - 10) self.port.move(left=self.ip.left, top=self.ip.bottom + 20) self.text_port_of_connection.move(centery=self.port.centery, right=self.port.left - 10) self.text_connection.move(centerx=self.frame.centerx, top=self.port.bottom + 5) self.button_connect.move(centerx=self.frame.centerx - (self.frame.width // 4), bottom=self.frame.bottom - 10) self.button_cancel.move(centerx=self.frame.centerx + (self.frame.width // 4), bottom=self.frame.bottom - 10) def connection(self): self.text_connection.show() self.text_connection.message = "Connection..." self.draw_and_refresh() if not self.connect_to_server(self.ip.get(), int(self.port.get()), 3): self.text_connection.message = "Connection failed. Try again." else: self.text_connection.hide() self.text_title.hide() self.button_connect.state = self.button_cancel.state = Button.DISABLED self.button_connect.focus_leave() self.lets_play_countdown.start(at_end=self.play) def play(self): self.start_game.mainloop() self.stop()
class PlayerClient(Dialog): def __init__(self, master, **kwargs): Dialog.__init__(self, master=master, bg_color=GREEN_DARK, **kwargs) self.start_game = master.start_game self.text_title = Text("Connect to Player 1", font=("calibri", 50)) self.form = Form(self) self.form.add_entry("IP", Text("IP address", font=("calibri", 40), color=YELLOW), Entry(self, width=15, font=("calibri", 30), bg=GREEN, highlight_color=YELLOW, outline=2)) self.form.add_entry("Port", Text("Port", font=("calibri", 40), color=YELLOW), Entry(self, width=15, font=("calibri", 30), bg=GREEN, highlight_color=YELLOW, outline=2)) self.text_connection = Text(font=("calibri", 25), color=YELLOW) self.text_connection.hide() self.button_connect = Button(self, "Connection", theme="option", callback=self.connection) self.button_cancel = Button(self, "Return to menu", theme="option", callback=self.stop) self.lets_play_countdown = CountDown(self, 3, "Connected.\nGame start in {seconds} seconds", font=("calibri", 35), color=YELLOW, justify="center") def on_quit(self): self.stop_connection() def place_objects(self): self.frame.move(center=self.center) self.text_title.move(centerx=self.frame.centerx, top=self.frame.top + 50) self.lets_play_countdown.move(center=self.text_title.center) self.form.move(center=self.frame.center) self.text_connection.move(centerx=self.frame.centerx, top=self.form.bottom + 5) self.button_connect.move(centerx=self.frame.centerx - (self.frame.width // 4), bottom=self.frame.bottom - 10) self.button_cancel.move(centerx=self.frame.centerx + (self.frame.width // 4), bottom=self.frame.bottom - 10) def connection(self): self.text_connection.show() self.text_connection.message = "Connection..." self.draw_and_refresh() try: address = self.form.get("IP") port = int(self.form.get("Port")) except ValueError: self.text_connection.message = "The port of connection must be a number." return if not self.connect_to_server(address, port, 3): self.text_connection.message = "Connection failed. Try again." else: self.text_connection.hide() self.text_title.hide() self.button_connect.state = self.button_cancel.state = Button.DISABLED self.button_connect.focus_leave() self.lets_play_countdown.start(at_end=self.play) def play(self): self.start_game.start(2) self.stop()
class PlayerServer(Dialog): def __init__(self, master, **kwargs): Dialog.__init__(self, master=master, bg_color=GREEN_DARK, **kwargs) params_for_all_buttons = { "font": ("calibri", 30), "bg": GREEN, "hover_bg": GREEN_LIGHT, "active_bg": GREEN_DARK, "outline": 3, "highlight_color": YELLOW } self.start_game = master.start_game self.text_title = Text("Waiting for Player 2", ("calibri", 50)) self.text_ip_address = Text(font=("calibri", 40)) self.text_port_of_connection = Text(font=("calibri", 40)) self.button_cancel = Button(self, "Return to menu", callback=self.stop, **params_for_all_buttons) self.lets_play_countdown = CountDown(self, 3, "Player 2 connected.\nGame start in {seconds} seconds", font=("calibri", 35), color=YELLOW, justify="center") def on_dialog_start_loop(self): try: ip, port = self.create_server(12800, 1) self.text_ip_address.message = f"IP address: {ip}" self.text_port_of_connection.message = f"Port: {port}" except OSError: self.stop() def on_dialog_quit(self): self.stop_connection() def place_objects(self): self.frame.move(center=self.center) self.text_title.move(centerx=self.frame.centerx, top=self.frame.top + 50) self.lets_play_countdown.move(center=self.text_title.center) self.text_ip_address.move(centerx=self.centerx, bottom=self.frame.centery - 10) self.text_port_of_connection.move(centerx=self.text_ip_address.centerx, top=self.text_ip_address.bottom + 20) self.button_cancel.move(centerx=self.frame.centerx, bottom=self.frame.bottom - 10) def update(self) -> None: if self.get_server_clients_count() > 1 and not self.lets_play_countdown.started(): self.set_server_listen(0) self.text_title.hide() self.button_cancel.state = Button.DISABLED self.lets_play_countdown.start(at_end=self.play) def play(self): self.start_game.mainloop() self.stop()
class NavySetup(Window): 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) @property def ships(self) -> Sequence[ShipSetup]: return self.ships_list.drawable @property def boxes(self) -> Sequence[BoxSetup]: return self.__boxes_list def start(self, player_id: int) -> None: self.gameplay.player_id = player_id self.mainloop(transition=self.transition) def on_start_loop(self) -> None: self.start_count_down() def on_quit(self) -> None: self.reinit_all_ships() def place_objects(self) -> None: self.button_back.move(x=20, y=20) self.count_down.move(top=20, right=self.right - 20) self.navy_grid.move(x=20, centery=self.centery) self.ships_list.move(left=self.navy_grid.right + 100, top=self.navy_grid.top + 30) self.button_restart.move(left=self.navy_grid.right + 20, bottom=self.navy_grid.bottom) self.button_random.move(left=self.button_restart.right + 20, bottom=self.navy_grid.bottom) self.button_play.move(right=self.right - 20, bottom=self.bottom - 20) for ship in self.ships: ship.default_center = ship.center def update(self): self.button_play.state = Button.NORMAL if all( ship.on_map for ship in self.ships) else Button.DISABLED if self.client_socket.recv("quit"): self.count_down.stop() self.enemy_quit_window.mainloop() self.stop() def create_setup(self) -> Sequence[dict[str, dict[str, Any]]]: setup = list() for ship in self.ships: setup.append({ "name": ship.name, "orient": ship.orient, "boxes": [box.pos for box in ship.boxes_covered] }) return setup def timeout(self): for ship in filter(lambda ship: not ship.on_map, self.ships): self.set_random_position_for_ship(ship) self.play() def play(self): if not all(ship.on_map for ship in self.ships): return if not self.client_socket.connected(): ai_navy_setup = NavySetup() ai_navy_setup.shuffle() ai_setup = ai_navy_setup.create_setup() else: ai_setup = None self.count_down.stop() self.client_socket.send("ready") WaitEnemy(self).mainloop() self.gameplay.start(self.create_setup(), ai_setup=ai_setup) self.reinit_all_ships() if self.gameplay.restart: self.start_count_down() else: self.stop() def reinit_all_ships(self): for ship in self.ships: ship.center = ship.default_center ship.orient = ShipSetup.HORIZONTAL ship.clear() def get_box(self, line: int, column: float) -> BoxSetup: return self.__boxes_dict.get((line, column)) def remove_boxes_highlight(self): for box in self.boxes: box.hover = False box.state = Button.NORMAL def get_valid_highlighted_boxes(self) -> Sequence[BoxSetup]: return list( filter(lambda box: box.hover and box.state == Button.NORMAL, self.boxes)) def highlight_boxes(self, ship: ShipSetup) -> None: boxes = list() for box in self.boxes: self.highlight_one_box(ship, box, len(boxes)) if box.hover is True: boxes.append(box) if len(boxes) != ship.ship_size or any(not self.valid_box(ship, box) for box in boxes): for box in boxes: box.state = Button.DISABLED def highlight_one_box(self, ship: ShipSetup, box: BoxSetup, nb_boxes_covered: int) -> None: box.hover = False box.state = Button.NORMAL if nb_boxes_covered == ship.ship_size: return if ship.orient == ShipSetup.HORIZONTAL: if (box.top <= ship.centery <= box.bottom) is False: return if ship.left > box.centerx or ship.right < box.centerx: return else: if (box.left <= ship.centerx <= box.right) is False: return if ship.top > box.centery or ship.bottom < box.centery: return box.hover = True def valid_box(self, ship: ShipSetup, box: BoxSetup) -> bool: line, column = box.pos offsets = [(-1, -1), (0, -1), (1, -1), (-1, 0), (0, 0), (1, 0), (-1, 1), (0, 1), (1, 1)] for u, v in offsets: box = self.get_box(line + u, column + v) if box is None: continue if box.ship is not None and box.ship != ship: return False return True def shuffle(self) -> None: self.reinit_all_ships() for ship in self.ships: self.set_random_position_for_ship(ship) def set_random_position_for_ship(self, ship: ShipSetup) -> None: ship.orient = random.choice([ShipSetup.HORIZONTAL, ShipSetup.VERTICAL]) first_box = random.choice(self.get_available_boxes(ship)) boxes = [first_box] for i in range(1, ship.ship_size): u = first_box.pos[ 0] + i if ship.orient == ShipSetup.VERTICAL else first_box.pos[ 0] v = first_box.pos[ 1] + i if ship.orient == ShipSetup.HORIZONTAL else first_box.pos[ 1] boxes.append(self.get_box(u, v)) ship.place_ship_on_map(boxes) def get_available_boxes(self, ship: ShipSetup): available_boxes = list() for box in self.boxes: if not self.valid_box(ship, box): continue line, column = box.pos valid = True for i in range(1, ship.ship_size): u = line + i if ship.orient == ShipSetup.VERTICAL else line v = column + i if ship.orient == ShipSetup.HORIZONTAL else column b = self.get_box(u, v) if b is None or (not self.valid_box(ship, b)): valid = False break if valid: available_boxes.append(box) return available_boxes
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()