2023-11-05 19:44:35 +01:00
|
|
|
#!/usr/bin/python3
|
|
|
|
|
|
|
|
import os
|
|
|
|
import pygame
|
2023-11-12 16:58:39 +01:00
|
|
|
import numpy
|
2024-02-25 15:09:15 +01:00
|
|
|
from wobbl_tools import pg # if you import pg from tools, you dont need to init pygame.
|
|
|
|
from wobbl_tools.data_file import DictFile
|
2023-12-29 21:10:43 +01:00
|
|
|
from physics.parabelfunc import berechneflugbahn
|
2023-11-05 19:44:35 +01:00
|
|
|
|
|
|
|
|
|
|
|
# integrated settings (I dont trust my own settings class.)
|
|
|
|
DEFAULT_WINDOW_SIZE = (800, 600)
|
|
|
|
FPS = 60
|
|
|
|
SAVE_SETTINGS_ON_EXIT = True
|
|
|
|
|
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
class Cowyeet:
|
|
|
|
def __init__(self):
|
|
|
|
self.DEFAULT_WINDOW_SIZE = DEFAULT_WINDOW_SIZE
|
|
|
|
self.FPS = FPS
|
|
|
|
self.SAVE_SETTINGS_ON_EXIT = SAVE_SETTINGS_ON_EXIT
|
|
|
|
|
|
|
|
pygame.init() # pygame initialization
|
|
|
|
self.screen = pygame.display.set_mode(DEFAULT_WINDOW_SIZE)
|
|
|
|
pygame.display.set_caption("Cowyeet 2.0")
|
|
|
|
|
|
|
|
self.settings = self.load_settings()
|
|
|
|
|
|
|
|
self.draw_loading_screen()
|
|
|
|
|
|
|
|
# variables
|
|
|
|
self.nero = (40, 40, 40) # colors (color names by https://www.color-blindness.com/color-name-hue/)
|
|
|
|
self.dim_gray = (100, 100, 100)
|
|
|
|
self.summer_sky = (50, 200, 220)
|
|
|
|
self.white_smoke = (240, 240, 240)
|
|
|
|
|
|
|
|
self.buttons = [] # misc
|
|
|
|
self.text_buttons = []
|
|
|
|
self.active_buttons = []
|
|
|
|
self.last_frame_mouse_pressed = False
|
|
|
|
self.page = "main_menu"
|
|
|
|
self.level = None
|
|
|
|
self.level_data = None
|
|
|
|
self.lvl_width = None
|
|
|
|
self.level_surface = None
|
|
|
|
self.cow_throwable = False
|
|
|
|
self.cow_flying = False
|
|
|
|
self.cow_position = (0, 0)
|
|
|
|
self.cow_flight_path = []
|
|
|
|
self.cow_flight_step = 0
|
|
|
|
self.pressed_keys = []
|
|
|
|
self.pressed_special_keys = pygame.key.get_mods()
|
|
|
|
|
|
|
|
# pygame objects
|
|
|
|
self.clock = pygame.time.Clock() # misc
|
|
|
|
self.mouse = pygame.mouse
|
|
|
|
self.keyboard = pygame.key
|
|
|
|
|
|
|
|
self.bigger_default_font = pygame.font.SysFont("ubuntu", 32) # fonts
|
|
|
|
|
|
|
|
self.choose_level_text = self.bigger_default_font.render("Choose a level:", True, self.white_smoke) # texts
|
|
|
|
|
|
|
|
self.texture_not_found = pygame.image.load("textures/texture_not_found.png")
|
|
|
|
self.texture_not_found = pygame.transform.scale(self.texture_not_found, (40 * self.settings["level_size_multiplier"], 40 * self.settings["level_size_multiplier"]))
|
|
|
|
|
|
|
|
self.icon_texture = self.load_texture("textures/icon.png")
|
|
|
|
self.full_icon_texture = self.load_texture("textures/icon_full.png")
|
|
|
|
self.full_icon_texture = pygame.transform.scale(self.full_icon_texture, (260, 90))
|
|
|
|
self.catapult_frame_texture = self.load_texture("textures/catapult/frame.png")
|
|
|
|
self.catapult_frame_texture = pygame.transform.scale(self.catapult_frame_texture, (66 * 5 * self.settings["level_size_multiplier"], 31 * 5 * self.settings["level_size_multiplier"]))
|
|
|
|
self.catapult_arm_texture = self.load_texture("textures/catapult/arm.png")
|
|
|
|
self.catapult_arm_texture = pygame.transform.scale(self.catapult_arm_texture, (25 * 5 * self.settings["level_size_multiplier"], 53 * 5 * self.settings["level_size_multiplier"]))
|
|
|
|
self.cow = self.load_texture("textures/cow/head.png")
|
|
|
|
self.cow = pygame.transform.scale(self.cow, (64 * 5 * self.settings["level_size_multiplier"], 64 * 5 * self.settings["level_size_multiplier"]))
|
|
|
|
self.stone_block_texture = self.load_block_texture("textures/terrain/stone_01.png")
|
|
|
|
self.dirt_block_texture = self.load_block_texture("textures/terrain/dirt_01.png")
|
|
|
|
self.grass_block_texture = self.load_block_texture("textures/terrain/grass_01.png")
|
|
|
|
self.rock_block_texture = self.load_block_texture("textures/terrain/rock_01.png")
|
|
|
|
|
|
|
|
# buttons
|
|
|
|
self.buttons.append(
|
|
|
|
pg.TextButton("Start", self.center, self.screen, lambda: self.page_switch("level_selector"), text_color=self.white_smoke, bg_color=self.dim_gray, font=self.bigger_default_font))
|
|
|
|
self.start_button = len(self.buttons) - 1
|
|
|
|
self.text_buttons.append(self.start_button)
|
|
|
|
|
|
|
|
self.buttons.append(
|
|
|
|
pg.TextButton("1", (128, 128), self.screen, lambda: self.start_level(1), text_color=self.white_smoke, bg_color=self.dim_gray, font=self.bigger_default_font, padding=(17, 8)))
|
|
|
|
self.lvl_one_button = len(self.buttons) - 1
|
|
|
|
self.text_buttons.append(self.lvl_one_button)
|
|
|
|
# /buttons
|
|
|
|
|
|
|
|
# /variables
|
|
|
|
|
|
|
|
# loading completed
|
|
|
|
|
|
|
|
self.screen = pygame.display.set_mode(self.settings["win_size"], flags=pygame.RESIZABLE, vsync=1)
|
|
|
|
self.window_size_reload(self.settings["win_size"])
|
|
|
|
pygame.display.set_icon(self.icon_texture)
|
|
|
|
self.screen.fill(self.nero)
|
|
|
|
pygame.display.update()
|
|
|
|
|
|
|
|
self.page_switch("main_menu")
|
|
|
|
|
|
|
|
self.running = True
|
|
|
|
|
|
|
|
def load_settings(self):
|
|
|
|
if os.path.isfile("settings.txt"): # If the settings exist, load them into a dict. Else create the settings with the default values.
|
2024-02-25 15:09:15 +01:00
|
|
|
settings = DictFile("settings.txt")
|
2023-11-05 19:44:35 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
else:
|
2024-02-25 15:09:15 +01:00
|
|
|
settings = DictFile()
|
2024-01-31 15:49:57 +01:00
|
|
|
settings.path = "settings.txt"
|
|
|
|
settings["win_size"] = DEFAULT_WINDOW_SIZE
|
|
|
|
settings["level_size_multiplier"] = 1
|
|
|
|
settings.save()
|
2023-11-09 17:20:51 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
return settings
|
2023-11-09 17:20:51 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def draw_loading_screen(self):
|
|
|
|
default_font = pygame.font.SysFont("ubuntu", 16)
|
|
|
|
loading_text = default_font.render("Loading...", True, (240, 240, 240))
|
2023-11-09 17:20:51 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.screen.fill((40, 40, 40))
|
|
|
|
self.screen.blit(loading_text, (400 - loading_text.get_width() / 2, 300 - loading_text.get_height() / 2))
|
|
|
|
pygame.display.update()
|
2023-11-09 17:20:51 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def load_texture(self, path: str):
|
|
|
|
if os.path.isfile(path):
|
|
|
|
return pygame.image.load(path)
|
2023-11-05 19:44:35 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
else:
|
|
|
|
return self.texture_not_found
|
2023-11-05 19:44:35 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def load_block_texture(self, path: str):
|
|
|
|
texture = self.load_texture(path)
|
2023-11-12 15:38:08 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
return pygame.transform.scale(texture, (40 * self.settings["level_size_multiplier"], 40 * self.settings["level_size_multiplier"]))
|
2023-11-12 15:38:08 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def center_x(self, width: int):
|
|
|
|
return self.screen.get_width() / 2 - width / 2
|
2023-12-27 17:45:27 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def center_y(self, height: int):
|
|
|
|
return self.screen.get_height() / 2 - height / 2
|
2023-12-27 17:45:27 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def center(self, size):
|
|
|
|
width, height = size
|
2023-11-12 15:38:08 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
return self.center_x(width), self.center_y(height)
|
2023-11-12 15:38:08 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def set_rot_point(self, img, pos):
|
|
|
|
w, h = img.get_size()
|
|
|
|
w, h = w * 2, h * 2
|
2023-11-12 16:58:39 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
img2 = pygame.Surface((w, h), pygame.SRCALPHA)
|
|
|
|
img2.blit(img, (w / 2 - pos[0], h / 2 - pos[1]))
|
|
|
|
return img2, (w, h)
|
2023-12-29 21:10:43 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def start_level(self, lvl: int):
|
|
|
|
self.page_switch("ingame")
|
|
|
|
self.level = lvl
|
2023-11-05 19:44:35 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.load_level(lvl)
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.cow_throwable = True
|
2023-11-12 16:58:39 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def load_level(self, lvl):
|
|
|
|
self.level_data = __import__("data.levels." + str(lvl), fromlist="data.levels")
|
2023-11-12 16:58:39 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.lvl_width, lvl_height = self.level_data.level_size
|
2023-11-12 15:38:08 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.level_surface = pygame.Surface((self.lvl_width * 40 * self.settings["level_size_multiplier"], lvl_height * 40 * self.settings["level_size_multiplier"]), pygame.SRCALPHA, 32)
|
2023-11-12 15:38:08 +01:00
|
|
|
|
2023-11-12 18:15:57 +01:00
|
|
|
x = 0
|
2024-01-31 15:49:57 +01:00
|
|
|
y = 0
|
2023-11-12 15:38:08 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
for row in self.level_data.data_array:
|
|
|
|
x = 0
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
for block in row:
|
|
|
|
rx = x * 40 * self.settings["level_size_multiplier"]
|
|
|
|
ry = y * 40 * self.settings["level_size_multiplier"]
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.blit_block(block, (rx, ry))
|
|
|
|
x += 1
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
y += 1
|
2023-11-12 15:38:08 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def blit_block(self, block, position: tuple):
|
|
|
|
if not block == 0:
|
|
|
|
if block == 1:
|
|
|
|
self.level_surface.blit(self.stone_block_texture, position)
|
2023-11-12 15:38:08 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
elif block == 2:
|
|
|
|
self.level_surface.blit(self.dirt_block_texture, position)
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
elif block == 3:
|
|
|
|
self.level_surface.blit(self.grass_block_texture, position)
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
elif block == 4:
|
|
|
|
self.level_surface.blit(self.rock_block_texture, position)
|
2023-12-29 21:10:43 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
else:
|
|
|
|
self.level_surface.blit(self.texture_not_found, position)
|
2023-12-29 21:10:43 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def yeet_cow(self):
|
|
|
|
if self.cow_throwable:
|
|
|
|
self.cow_throwable = False
|
2023-12-29 21:10:43 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
lvl_x, lvl_y = self.level_data.level_size
|
|
|
|
lvl_x *= 40
|
|
|
|
lvl_x *= self.settings["level_size_multiplier"]
|
|
|
|
lvl_y *= 40
|
|
|
|
lvl_y *= self.settings["level_size_multiplier"]
|
2023-12-29 21:10:43 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.cow_flight_path = berechneflugbahn(lvl_x, lvl_y, lvl_x + 100, 34, 128, xstep=10)
|
2023-12-29 21:10:43 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.cow_flying = True
|
|
|
|
self.cow_flight_step = 0
|
2023-12-29 21:10:43 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def main_menu_page(self):
|
|
|
|
self.screen.blit(self.full_icon_texture, (self.center_x(self.full_icon_texture.get_width()), 128))
|
|
|
|
self.buttons[self.start_button].blit()
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def level_selector_page(self):
|
|
|
|
self.buttons[self.lvl_one_button].blit()
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.screen.blit(self.choose_level_text, (self.screen.get_width() / 2 - self.choose_level_text.get_width() / 2, 16))
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def ingame_page(self):
|
|
|
|
self.screen.fill(self.summer_sky)
|
|
|
|
self.screen.blit(self.level_surface, (0, self.screen.get_height() - self.level_surface.get_height()))
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
lvl_size = self.settings["level_size_multiplier"]
|
2023-11-12 15:38:08 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
cx, cy = self.level_data.catapult_pos
|
|
|
|
cx = cx * 40 * lvl_size
|
|
|
|
cy = self.screen.get_height() - self.level_surface.get_height() + cy * 40 * lvl_size - self.catapult_frame_texture.get_height()
|
2023-11-09 17:20:51 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.screen.blit(self.catapult_frame_texture, (cx, cy))
|
|
|
|
self.screen.blit(self.catapult_arm_texture, (cx + 27 * 5 * lvl_size, cy - 35 * 5 * lvl_size))
|
2023-11-09 17:20:51 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
if self.cow_flying:
|
|
|
|
self.cow_flight_step += 1
|
2023-12-29 21:10:43 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
x = self.cow_flight_path[self.cow_flight_step * 2]
|
|
|
|
y = self.cow_flight_path[self.cow_flight_step * 2 + 1]
|
|
|
|
self.cow_position = (x, self.screen.get_height() - y - 64 * 5 * self.settings["level_size_multiplier"])
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
if self.cow_flight_step * 2 == len(self.cow_flight_path) - 2:
|
|
|
|
self.cow_flying = False
|
|
|
|
self.cow_throwable = True
|
2023-12-27 17:45:27 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.screen.blit(self.cow, self.cow_position)
|
2023-11-19 17:02:17 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def page_selector(self):
|
|
|
|
if self.page == "main_menu":
|
|
|
|
self.main_menu_page()
|
2023-11-19 17:02:17 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
elif self.page == "level_selector":
|
|
|
|
self.level_selector_page()
|
2023-12-29 21:10:43 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
elif self.page == "ingame":
|
|
|
|
self.ingame_page()
|
2023-12-29 21:10:43 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def page_switch(self, new_page: str=None):
|
|
|
|
if not new_page is None:
|
|
|
|
self.page = new_page
|
2023-12-29 21:10:43 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
if self.page == "main_menu":
|
|
|
|
self.active_buttons = [self.start_button]
|
2023-12-29 21:10:43 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
elif self.page == "level_selector":
|
|
|
|
self.active_buttons = [self.lvl_one_button]
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
elif self.page == "ingame":
|
|
|
|
self.active_buttons = []
|
2023-11-09 17:20:51 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
# for button in buttons:
|
|
|
|
# button.active = False
|
|
|
|
#
|
|
|
|
# for button in active_buttons:
|
|
|
|
# button.active = True
|
2023-11-12 15:38:08 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def close(self):
|
|
|
|
self.running = False
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def window_size_reload(self, new_size):
|
|
|
|
for button in self.text_buttons:
|
|
|
|
self.buttons[button].update()
|
2023-11-09 17:20:51 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.settings["win_size"] = new_size
|
2023-11-09 17:20:51 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def get_events(self):
|
|
|
|
for event in pygame.event.get():
|
|
|
|
if event.type == pygame.QUIT:
|
|
|
|
self.close()
|
|
|
|
return
|
2023-11-09 17:20:51 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
if event.type == pygame.MOUSEBUTTONDOWN:
|
|
|
|
pressed = self.mouse.get_pressed()
|
|
|
|
pos = self.mouse.get_pos()
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
for button in self.active_buttons:
|
|
|
|
self.buttons[button].check(pos, pressed)
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.last_frame_mouse_pressed = True
|
2023-11-12 18:15:57 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
elif event.type == pygame.VIDEORESIZE:
|
2023-12-27 17:45:27 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.window_size_reload(event.size)
|
2023-12-27 17:45:27 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
elif event.type == pygame.KEYDOWN:
|
|
|
|
key = event.key
|
2023-12-27 17:45:27 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
if key == pygame.K_c and self.pressed_special_keys & pygame.KMOD_CTRL:
|
|
|
|
self.close()
|
2023-12-27 17:45:27 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
elif key == pygame.K_SPACE:
|
|
|
|
if self.page == "ingame":
|
|
|
|
self.yeet_cow()
|
2023-12-27 17:45:27 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
def loop(self):
|
|
|
|
self.screen.fill(self.nero)
|
2023-11-09 17:20:51 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.pressed_keys = self.keyboard.get_pressed()
|
|
|
|
self.pressed_special_keys = self.keyboard.get_mods()
|
|
|
|
self.get_events()
|
2023-11-09 17:20:51 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
if not self.running:
|
2023-11-05 19:44:35 +01:00
|
|
|
return
|
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.page_selector()
|
2023-12-27 17:45:27 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
pygame.display.update()
|
2023-11-05 19:44:35 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
self.clock.tick(self.FPS)
|
2023-11-09 17:20:51 +01:00
|
|
|
|
2023-11-05 19:44:35 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
if __name__ == "__main__":
|
|
|
|
game = Cowyeet()
|
2023-11-05 19:44:35 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
while game.running:
|
|
|
|
game.loop()
|
2023-11-05 19:44:35 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
pygame.quit()
|
2023-11-05 19:44:35 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
if game.SAVE_SETTINGS_ON_EXIT:
|
|
|
|
game.settings.save()
|
2024-01-02 14:24:10 +01:00
|
|
|
|
2024-01-31 15:49:57 +01:00
|
|
|
print("Bye!")
|