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