import random import pygame import json from pathlib import Path pygame.init() WIDTH, HEIGHT = 640, 480 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Sugarmania") clock = pygame.time.Clock() font = pygame.font.SysFont(None, 36) big_font = pygame.font.SysFont(None, 64) small_font = pygame.font.SysFont(None, 26) tiny_font = pygame.font.SysFont(None, 20) MENU_OPTIONS = ["Play", "Quit"] MODE_NAMES = ["easy", "medium", "hard"] MODES = { "easy": {"lives": 5, "speed": 7, "spawn_every": 10, "enemy_pool": ["Bouncer"], "desc": "Only Bouncers. One new enemy per 10 sugar"}, "medium": {"lives": 3, "speed": 6, "spawn_every": 7, "enemy_pool": ["Bouncer", "Chaser"], "desc": "Bouncers and Chasers. One new enemy per 7 sugar"}, "hard": {"lives": 2, "speed": 5, "spawn_every": 5, "enemy_pool": ["Bouncer", "Chaser", "Dasher"], "desc": "Bouncers, Chasers and Dashers. One new enemy per 5 sugar"} } class ChaserEnemy: def __init__(self, avoid_rect): self.rect = pygame.Rect(0, 0, 36, 36) while True: self.rect.x = random.randint(0, WIDTH - 36) self.rect.y = random.randint(0, HEIGHT - 36) if abs(self.rect.x - avoid_rect.x) > 150 or abs(self.rect.y - avoid_rect.y) > 150: break self.speed = 1 self.spawn_timer = 60 def update(self, target): if self.rect.centerx < target.centerx: self.rect.x += self.speed elif self.rect.centerx > target.centerx: self.rect.x -= self.speed if self.rect.y < target.centery: self.rect.y += self.speed elif self.rect.centery > target.centery: self.rect.y -= self.speed class DasherEnemy: WAIT_BEFORE_FIRST_DASH = 90 DASH_FRAMES = 30 WAIT_AFTER_DASH = 120 DASH_SPEED = 6 def __init__(self, avoid_rect): self.rect = pygame.Rect(0, 0, 36, 36) while True: self.rect.x = random.randint(0, WIDTH - 36) self.rect.y = random.randint(0, HEIGHT - 36) if abs(self.rect.x - avoid_rect.x) > 150 or abs(self.rect.y - avoid_rect.y) > 150: break self.x = float(self.rect.x) self.y = float(self.rect.y) self.vx = 0 self.vy = 0 self.state = "wait" self.timer = self.WAIT_BEFORE_FIRST_DASH self.spawn_timer = 60 def update(self, target): self.timer -= 1 if self.state == "wait": if self.timer > 0: return dx = target.centerx - self.rect.centerx dy = target.centery - self.rect.centery dist = max(1, (dx ** 2 + dy ** 2) ** 0.5) self.vx = dx / dist * self.DASH_SPEED self.vy = dy / dist * self.DASH_SPEED self.state = "dash" self.timer = self.DASH_FRAMES return self.x += self.vx self.y += self.vy self.rect.x = int(self.x) self.rect.y = int(self.y) self.rect.clamp_ip(screen.get_rect()) self.x, self.y = float(self.rect.x), float(self.rect.y) if self.timer <= 0: self.state = "wait" self.vx = 0 self.vy = 0 self.timer = self.WAIT_AFTER_DASH class BouncerEnemy: def __init__(self, avoid_rect): self.rect = pygame.Rect(0, 0, 36, 36) while True: self.rect.x = random.randint(0, WIDTH - 36) self.rect.y = random.randint(0, HEIGHT - 36) if abs(self.rect.x - avoid_rect.x) > 150 or abs(self.rect.y - avoid_rect.y) > 150: break self.vx = random.choice([-3, -2, 2, 3]) self.vy = random.choice([-3, -2, 2, 3]) self.spawn_timer = 60 def update(self, target=None): self.rect.x += self.vx self.rect.y += self.vy if self.rect.left < 0 or self.rect.right > WIDTH: self.vx = -self.vx if self.rect.top < 0 or self.rect.bottom > HEIGHT: self.vy = -self.vy ENEMY_CLASSES = {"Bouncer": BouncerEnemy, "Chaser": ChaserEnemy, "Dasher": DasherEnemy} player_img = pygame.image.load("player.png").convert_alpha() player_img = pygame.transform.scale(player_img, (40, 60)) Bouncer_img = pygame.image.load("Bouncer.png").convert_alpha() Bouncer_img = pygame.transform.scale(Bouncer_img, (30, 45)) Chaser_img = pygame.image.load("Chaser.png").convert_alpha() Chaser_img = pygame.transform.scale(Chaser_img, (30, 45)) Dasher_img = pygame.image.load("Dasher.png").convert_alpha() Dasher_img = pygame.transform.scale(Dasher_img, (30, 45)) ENEMY_IMAGES = { "Bouncer": Bouncer_img, "Dasher": Dasher_img, "Chaser": Chaser_img } sugar_img = pygame.image.load("honey.png").convert_alpha() sugar_img = pygame.transform.scale(sugar_img, (50, 50)) heart_img = pygame.image.load("Heart.png").convert_alpha() heart_img = pygame.transform.scale(heart_img,(40, 40)) background_img = pygame.image.load("background.png").convert_alpha() background_img = pygame.transform.scale(background_img, (WIDTH, HEIGHT)) title_img = pygame.image.load("titles.png").convert_alpha() button_img = pygame.image.load("button.png").convert_alpha() button_hover_img = pygame.image.load("button_hover.png").convert_alpha() quit_img = pygame.image.load("quit.png").convert_alpha() quit_hover_img = pygame.image.load("quit_hover.png").convert_alpha() panel_img = pygame.image.load("panel.png").convert_alpha() def new_sugar(): return pygame.Rect(random.randint(0, WIDTH - 20), random.randint(0, HEIGHT - 20), 50, 50) #20,s SAVE_PATH = Path(__file__).with_name("highscores.json") def load_best(mode): try: scores = json.loads(SAVE_PATH.read_text()) except (FileNotFoundError, json.JSONDecodeError): return 0 return scores.get(mode, 0) def save_best(mode, score): try: scores = json.loads(SAVE_PATH.read_text()) except (FileNotFoundError, json.JSONDecodeError): scores = {} scores[mode] = score SAVE_PATH.write_text(json.dumps(scores)) #game loop def game_screen(mode): cfg = MODES[mode] player = pygame.Rect(300, 220, 40, 60) speed = cfg["speed"] score = 0 Enemies = [] sugar = new_sugar() lives = cfg["lives"] hit_timer = 0 start_ticks = pygame.time.get_ticks() while True: for event in pygame.event.get(): if event.type == pygame.QUIT: return "quit", {} if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: return "menu", {} #movement keys = pygame.key.get_pressed() if keys[pygame.K_LEFT] or keys[pygame.K_a]: player.x -= speed if keys[pygame.K_RIGHT] or keys[pygame.K_d]: player.x += speed if keys[pygame.K_UP] or keys[pygame.K_w]: player.y -= speed if keys[pygame.K_DOWN] or keys[pygame.K_s]: player.y += speed for enemy in Enemies: if enemy.spawn_timer > 0: enemy.spawn_timer -= 1 else: enemy.update(player) if hit_timer > 0: hit_timer -= 1 else: for enemy in Enemies: if enemy.spawn_timer <= 0 and player.colliderect(enemy.rect): lives -= 1 hit_timer = 90 break if lives <= 0: return "gameover", { "score": score, "snapshot": screen.copy(), "seconds": (pygame.time.get_ticks() - start_ticks) // 1000, } #keep player in player.clamp_ip(screen.get_rect()) # score if player.colliderect(sugar): score += 1 sugar = new_sugar() if score % cfg["spawn_every"] == 0 and cfg["enemy_pool"]: enemy_name = random.choice(cfg["enemy_pool"]) enemy = ENEMY_CLASSES[enemy_name](player) enemy.image = ENEMY_IMAGES[enemy_name] Enemies.append(enemy) screen.fill((255, 228, 240)) # screen screen.blit(sugar_img, sugar) #sugar text = font.render(f"score: {score}", True, (90, 30, 60)) screen.blit(text, (10, 10)) for enemy in Enemies: if enemy.spawn_timer % 10 < 5: screen.blit(enemy.image, enemy.image.get_rect(center=enemy.rect.center)) screen.blit(player_img, player) if hit_timer > 0 and hit_timer % 10 < 5: red = player_img.copy() red.fill((255, 40, 40, 0), special_flags=pygame.BLEND_RGBA_ADD) screen.blit(red, player) for i in range(lives): screen.blit(heart_img, (10 + i * 35, 40)) pygame.display.flip() clock.tick(60) class MenuButton: def __init__(self, center, action, image, hover_image): self.action = action self.image = image self.hover_image = hover_image self.rect = self.image.get_rect(center = center) def hovered(self, mouse_pos): return self.rect.collidepoint(mouse_pos) def draw(self, surface, highlighted): picture = self.hover_image if highlighted else self.image surface.blit(picture, self.rect) def menu_screen(): selected = 0 buttons = [ MenuButton((WIDTH // 2, 250), "mode", button_img, button_hover_img), MenuButton((WIDTH // 2, 340), "quit", quit_img, quit_hover_img), ] while True: for event in pygame.event.get(): if event.type == pygame.QUIT: return "quit" if event.type == pygame.KEYDOWN: if event.key in (pygame.K_UP, pygame.K_w): selected = (selected - 1) % len(buttons) elif event.key in (pygame.K_DOWN, pygame.K_s): selected = (selected + 1) % len(buttons) elif event.key == pygame.K_RETURN: if selected == -1: selected = 0 return buttons [selected].action elif event.key == pygame.K_ESCAPE: return "quit" if event.type == pygame.MOUSEMOTION: selected = -1 for i, button in enumerate(buttons): if button.hovered(event.pos): selected = i if event.type == pygame.WINDOWLEAVE: selected = -1 if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: for button in buttons: if button.hovered(event.pos): return button.action screen.blit(background_img, (0, 0)) screen.blit(title_img, title_img.get_rect(center=(WIDTH // 2, 80))) for i, button in enumerate(buttons): button.draw(screen, i == selected) pygame.display.flip() clock.tick(60) def mode_screen(): selected = 0 hovered = -1 play_button = MenuButton((210, 405), "game", button_img, button_hover_img) back_button = MenuButton((430, 405), "menu", quit_img, quit_hover_img) buttons = [play_button, back_button] panel_rect = panel_img.get_rect(center=(WIDTH // 2, 225)) slot_rects = [ pygame.Rect(panel_rect.x + 22, panel_rect.y + 22, 78, 68), pygame.Rect(panel_rect.x + 133, panel_rect.y + 22, 77, 68), pygame.Rect(panel_rect.x + 234, panel_rect.y + 28, 73, 66), ] while True: for event in pygame.event.get(): if event.type == pygame.QUIT: return "quit", None if event.type == pygame.KEYDOWN: if event.key in (pygame.K_LEFT, pygame.K_a): selected = (selected - 1) % len(MODE_NAMES) elif event.key in (pygame.K_RIGHT, pygame.K_d): selected = (selected + 1) % len(MODE_NAMES) elif event.key == pygame.K_RETURN: return "game", MODE_NAMES[selected] elif event.key == pygame.K_ESCAPE: return "menu", None if event.type == pygame.MOUSEMOTION: hovered = -1 for i, button in enumerate(buttons): if button.hovered(event.pos): hovered = i if event.type == pygame.WINDOWLEAVE: hovered = -1 if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: for i, slot in enumerate(slot_rects): if slot.collidepoint(event.pos): selected = i for button in buttons: if button.hovered(event.pos): if button.action == "game": return "game", MODE_NAMES[selected] return "menu", None screen.blit(background_img, (0, 0)) screen.blit(title_img, title_img.get_rect(center=(WIDTH // 2, 70))) pygame.draw.rect(screen, (255, 129, 195), panel_rect) screen.blit(panel_img, panel_rect) for i, slot in enumerate(slot_rects): fill = (255, 200, 225) if i == selected else (227, 89, 161) pygame.draw.rect(screen, fill, slot.inflate (-4, -4), border_radius=6) for i, slot in enumerate(slot_rects): name = small_font.render(MODE_NAMES[i].upper(), True, (255, 240, 8)) screen.blit(name, name.get_rect(center=slot.center)) cfg = MODES[MODE_NAMES[selected]] first_sentence, second_sentence = cfg["desc"].split(". ", 1) line1 = tiny_font.render(first_sentence + ".", True, (255, 240, 8)) line2 = tiny_font.render(second_sentence, True, (255, 240, 8)) screen.blit(line1, line1.get_rect(center=(panel_rect.centerx, panel_rect.top + 108))) screen.blit(line2, line2.get_rect(center=(panel_rect.centerx, panel_rect.top + 132))) for i, button in enumerate(buttons): button.draw(screen, i == hovered) pygame.display.flip() clock.tick(60) def game_over_screen(mode, result): snapshot = result["snapshot"] score = result["score"] seconds = result.get("seconds", 0) best = load_best(mode) new_best = score > best if new_best: save_best(mode, score) best = score overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA) fade = 0 while True: for event in pygame.event.get(): if event.type == pygame.QUIT: return "quit", {} if event.type == pygame.KEYDOWN: if event.key in (pygame.K_RETURN, pygame.K_r): return "game", {} elif event.key == pygame.K_m: return "mode", {} elif event.key == pygame.K_ESCAPE: return "menu", {} elif event.key == pygame.K_q: return "quit", {} if fade < 180: fade = min(180, fade + 12) screen.blit(snapshot, (0, 0)) overlay.fill((0, 0, 0, fade)) screen.blit(overlay, (0, 0)) title = big_font.render("GAME OVER", True, (255, 240, 8)) screen.blit(title, title.get_rect(center=(WIDTH // 2, 130))) score_text = font.render(f"Score: {score}", True, (255, 255, 255)) screen.blit(score_text, score_text.get_rect(center=(WIDTH // 2, 205))) if new_best: best_text = small_font.render("NEW BEST", True, (255, 240, 8)) else: best_text = small_font.render(f"best {best}", True, (255, 255, 255)) screen.blit(best_text, best_text.get_rect(center=(WIDTH // 2, 245))) stats = small_font.render( f"{mode} ยท {seconds}s survived", True, (255, 255, 255) ) screen.blit(stats, stats.get_rect(center=(WIDTH // 2, 285))) prompts = [ "ENTER-RETRY", "M-change mode", "esc-main menu", "Q-quit", ] for i, text in enumerate(prompts): prompt = font.render(text, True, (255, 255, 255)) screen.blit(prompt, prompt.get_rect(center=(WIDTH // 2, 345 + i * 36))) pygame.display.flip() clock.tick(60) if __name__ == "__main__": state = "menu" mode = None payload = {} while state != "quit": if state == "menu": state = menu_screen() elif state == "mode": state, mode = mode_screen() elif state == "game": state, payload = game_screen(mode) elif state == "gameover": state, payload = game_over_screen(mode, payload) pygame.quit()