INITIALIZING ENGINE...
import pygame import sys import asyncio import math import json # Try to import requests to fix the SSL/HTTPS error try: import requests HAS_REQUESTS = True except ImportError: HAS_REQUESTS = False print("WARNING: 'requests' library not found. Open your terminal and run 'pip install requests' to fix the HTTPS error.") # Initialize Pygame pygame.init() # Screen Constants WIDTH, HEIGHT = 800, 600 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Circle Sam: Creator Edition") clock = pygame.time.Clock() # Back4App Credentials APP_ID = "Y4o25IXpTykudD2AVi0p748IVCJGQj1yhn30Kjmz" JS_KEY = "JgqzNrfnD76ugkEBSh4SBys3SugLVZe6Ysq7Gy5T" API_URL = "https://parseapi.back4app.com/classes/Level" # Physics Constants BALL_RADIUS = 20 GRAVITY = 0.5 BOUNCE = -0.65 WALL_BOUNCE = -0.7 JUMP_SPEED = -12.5 SUPER_BOUNCE = -17.5 MEGA_BOUNCE = -24.0 DEFAULT_ACCEL = 0.6 DEFAULT_MAX_SPEED = 9 DEFAULT_FRICTION = 0.92 # Fonts font_title = pygame.font.Font(None, 74) font_prompt = pygame.font.Font(None, 36) font_intro = pygame.font.Font(None, 90) font_editor = pygame.font.Font(None, 24) font_browser = pygame.font.Font(None, 28) # --- LEVEL SERIALIZATION --- def rect_to_dict(r): return {"x": r.x, "y": r.y, "w": r.width, "h": r.height} def dict_to_rect(d): return pygame.Rect(d["x"], d["y"], d["w"], d["h"]) def serialize_level(level_data, name="My Awesome Level", author="Creator"): out = {"name": name, "author": author, "width": level_data["width"], "height": level_data["height"], "start": level_data["start"]} block_types = ["platforms", "spikes", "ice_blocks", "mud_blocks", "bounce_blocks", "jump_pads", "fake_walls", "conveyor_right", "conveyor_left", "coins", "keys", "doors"] for key in block_types: out[key] = [rect_to_dict(r) for r in level_data.get(key, [])] if "goal" in level_data: out["goal"] = rect_to_dict(level_data["goal"]) out["bosses"] = [{"rect": rect_to_dict(b["rect"]), "vx": b["vx"], "vy": b["vy"], "start_x": b["start_x"], "start_y": b["start_y"]} for b in level_data.get("bosses", [])] return out def deserialize_level(data): level = {"width": data.get("width", 2000), "height": data.get("height", 1200), "start": tuple(data.get("start", (100, 1000))), "name": data.get("name", "Unknown")} block_types = ["platforms", "spikes", "ice_blocks", "mud_blocks", "bounce_blocks", "jump_pads", "fake_walls", "conveyor_right", "conveyor_left", "coins", "keys", "doors"] for key in block_types: level[key] = [dict_to_rect(r) for r in data.get(key, [])] if "goal" in data: level["goal"] = dict_to_rect(data["goal"]) else: level["goal"] = pygame.Rect(1800, 1000, 60, 60) level["bosses"] = [{"rect": dict_to_rect(b["rect"]), "vx": b["vx"], "vy": b["vy"], "start_x": b["start_x"], "start_y": b["start_y"]} for b in data.get("bosses", [])] return level # --- BACK4APP NETWORKING --- def _do_fetch(): if not HAS_REQUESTS: return [] try: response = requests.get(API_URL, headers={"X-Parse-Application-Id": APP_ID, "X-Parse-Javascript-Key": JS_KEY}, timeout=5) return response.json().get('results', []) except Exception as e: print("Network Error:", e) return [] def _do_upload(payload): if not HAS_REQUESTS: return False try: response = requests.post(API_URL, json=payload, headers={"X-Parse-Application-Id": APP_ID, "X-Parse-Javascript-Key": JS_KEY, "Content-Type": "application/json"}, timeout=5) return response.status_code in (200, 201) except Exception as e: print("Upload Error:", e) return False # Official Campaign Levels (Including User's New Custom Level at the end!) levels = [ { "width": 1200, "height": 600, "start": (100, 500), "platforms": [pygame.Rect(0, 580, 1200, 20), pygame.Rect(400, 480, 150, 20), pygame.Rect(750, 380, 150, 20)], "goal": pygame.Rect(1000, 300, 60, 60) }, { "width": 1400, "height": 600, "start": (100, 500), "platforms": [pygame.Rect(0, 580, 300, 20), pygame.Rect(550, 450, 150, 20), pygame.Rect(950, 350, 150, 20), pygame.Rect(1200, 580, 200, 20)], "spikes": [pygame.Rect(300, 550, 900, 30)], "goal": pygame.Rect(1250, 500, 60, 60) }, { "width": 1200, "height": 800, "start": (100, 700), "platforms": [pygame.Rect(0, 780, 400, 20), pygame.Rect(800, 300, 200, 20)], "spikes": [pygame.Rect(400, 750, 800, 30)], "bounce_blocks": [pygame.Rect(450, 760, 80, 20), pygame.Rect(700, 550, 80, 20)], "goal": pygame.Rect(850, 220, 60, 60) }, { "width": 800, "height": 1200, "start": (400, 1100), "platforms": [pygame.Rect(0, 1180, 800, 20), pygame.Rect(100, 800, 100, 20), pygame.Rect(600, 500, 100, 20)], "spikes": [pygame.Rect(0, 1150, 300, 30), pygame.Rect(500, 1150, 300, 30)], "ice_blocks": [pygame.Rect(300, 1000, 200, 20), pygame.Rect(300, 850, 200, 20), pygame.Rect(300, 700, 200, 20), pygame.Rect(300, 550, 200, 20), pygame.Rect(300, 400, 200, 20)], "goal": pygame.Rect(370, 300, 60, 60) }, { "width": 1600, "height": 600, "start": (100, 500), "platforms": [pygame.Rect(0, 580, 200, 20), pygame.Rect(1400, 580, 200, 20)], "spikes": [pygame.Rect(200, 550, 1200, 30)], "mud_blocks": [pygame.Rect(350, 480, 80, 20), pygame.Rect(650, 380, 80, 20), pygame.Rect(950, 280, 80, 20), pygame.Rect(1250, 450, 80, 20)], "goal": pygame.Rect(1450, 500, 60, 60) }, { "width": 1600, "height": 600, "start": (100, 500), "platforms": [pygame.Rect(0, 580, 200, 20), pygame.Rect(1400, 580, 200, 20)], "spikes": [pygame.Rect(200, 550, 1200, 30)], "conveyor_right": [pygame.Rect(300, 480, 150, 20), pygame.Rect(900, 480, 150, 20)], "conveyor_left": [pygame.Rect(600, 350, 150, 20), pygame.Rect(1200, 350, 150, 20)], "bounce_blocks": [pygame.Rect(450, 480, 50, 20), pygame.Rect(1050, 480, 50, 20)], "goal": pygame.Rect(1450, 500, 60, 60) }, { "width": 2000, "height": 1000, "start": (100, 900), "platforms": [pygame.Rect(0, 980, 300, 20), pygame.Rect(1800, 200, 200, 20)], "spikes": [pygame.Rect(300, 950, 1700, 30)], "ice_blocks": [pygame.Rect(800, 600, 150, 20)], "conveyor_right": [pygame.Rect(1050, 550, 100, 20)], "mud_blocks": [pygame.Rect(1300, 450, 80, 20)], "bounce_blocks": [pygame.Rect(450, 850, 60, 20), pygame.Rect(1500, 400, 60, 20)], "goal": pygame.Rect(1850, 120, 60, 60) }, { "width": 1200, "height": 800, "start": (100, 700), "platforms": [pygame.Rect(0, 780, 1200, 20), pygame.Rect(0, 0, 1200, 20), pygame.Rect(0, 0, 20, 800), pygame.Rect(1180, 0, 20, 800)], "bounce_blocks": [pygame.Rect(50, 550, 60, 20), pygame.Rect(1090, 550, 60, 20)], "goal": pygame.Rect(570, 100, 60, 60), "bosses": [{"rect": pygame.Rect(500, 500, 150, 150), "vx": 5, "vy": -5, "start_x": 500, "start_y": 500}] }, { "width": 1600, "height": 1200, "start": (100, 1100), "platforms": [pygame.Rect(0, 1180, 200, 20)], "spikes": [pygame.Rect(200, 1150, 1400, 30)], "ice_blocks": [pygame.Rect(300, 1000, 100, 20), pygame.Rect(500, 850, 100, 20), pygame.Rect(700, 700, 100, 20), pygame.Rect(900, 550, 100, 20)], "bounce_blocks": [pygame.Rect(1100, 550, 50, 20)], "goal": pygame.Rect(1400, 200, 60, 60) }, { "width": 2000, "height": 800, "start": (100, 700), "platforms": [pygame.Rect(0, 780, 200, 20)], "spikes": [pygame.Rect(200, 750, 1800, 30)], "conveyor_left": [pygame.Rect(300, 600, 300, 20), pygame.Rect(1000, 400, 300, 20)], "conveyor_right": [pygame.Rect(650, 500, 300, 20), pygame.Rect(1350, 300, 300, 20)], "goal": pygame.Rect(1800, 200, 60, 60) }, { "width": 1000, "height": 1600, "start": (100, 1500), "platforms": [pygame.Rect(0, 1580, 1000, 20)], "mud_blocks": [pygame.Rect(200, 1400, 80, 20), pygame.Rect(700, 1250, 80, 20), pygame.Rect(200, 1100, 80, 20), pygame.Rect(700, 950, 80, 20), pygame.Rect(200, 800, 80, 20), pygame.Rect(700, 650, 80, 20)], "spikes": [pygame.Rect(300, 1270, 400, 30), pygame.Rect(0, 1070, 600, 30), pygame.Rect(300, 870, 700, 30)], "goal": pygame.Rect(450, 500, 60, 60) }, { "width": 1800, "height": 800, "start": (50, 700), "platforms": [pygame.Rect(0, 780, 150, 20)], "spikes": [pygame.Rect(150, 750, 1650, 30)], "bounce_blocks": [pygame.Rect(300, 760, 40, 20), pygame.Rect(600, 500, 40, 20), pygame.Rect(900, 760, 40, 20), pygame.Rect(1200, 400, 40, 20)], "mud_blocks": [pygame.Rect(1500, 300, 100, 20)], "goal": pygame.Rect(1650, 200, 60, 60) }, { "width": 1400, "height": 1400, "start": (100, 1300), "platforms": [pygame.Rect(0, 1380, 200, 20)], "spikes": [pygame.Rect(200, 1350, 1200, 30)], "conveyor_right": [pygame.Rect(300, 1200, 200, 20), pygame.Rect(900, 800, 200, 20)], "ice_blocks": [pygame.Rect(600, 1000, 200, 20)], "bounce_blocks": [pygame.Rect(1200, 800, 50, 20), pygame.Rect(200, 600, 50, 20)], "goal": pygame.Rect(700, 300, 60, 60) }, { "width": 2400, "height": 800, "start": (100, 700), "platforms": [pygame.Rect(0, 780, 300, 20), pygame.Rect(2000, 780, 400, 20)], "spikes": [pygame.Rect(300, 750, 1700, 30)], "bounce_blocks": [pygame.Rect(500, 760, 80, 20), pygame.Rect(900, 760, 80, 20), pygame.Rect(1300, 760, 80, 20), pygame.Rect(1700, 760, 80, 20)], "goal": pygame.Rect(2200, 680, 60, 60) }, { "width": 800, "height": 2000, "start": (100, 1900), "platforms": [pygame.Rect(0, 1980, 800, 20)], "conveyor_right": [pygame.Rect(100, 1700, 150, 20), pygame.Rect(100, 1300, 150, 20), pygame.Rect(100, 900, 150, 20)], "conveyor_left": [pygame.Rect(550, 1500, 150, 20), pygame.Rect(550, 1100, 150, 20), pygame.Rect(550, 700, 150, 20)], "goal": pygame.Rect(370, 300, 60, 60) }, { "width": 1600, "height": 1000, "start": (100, 900), "platforms": [pygame.Rect(0, 980, 200, 20)], "spikes": [pygame.Rect(200, 950, 1400, 30)], "mud_blocks": [pygame.Rect(300, 800, 50, 20), pygame.Rect(500, 650, 50, 20), pygame.Rect(700, 500, 50, 20), pygame.Rect(900, 350, 50, 20)], "bounce_blocks": [pygame.Rect(1100, 350, 40, 20)], "goal": pygame.Rect(1400, 150, 60, 60) }, { "width": 2400, "height": 1200, "start": (100, 1100), "platforms": [pygame.Rect(0, 1180, 200, 20), pygame.Rect(2200, 400, 200, 20)], "spikes": [pygame.Rect(200, 1150, 2200, 30), pygame.Rect(600, 950, 400, 30)], "ice_blocks": [pygame.Rect(300, 1000, 150, 20), pygame.Rect(650, 900, 150, 20), pygame.Rect(1000, 800, 150, 20)], "mud_blocks": [pygame.Rect(1300, 700, 100, 20)], "conveyor_right": [pygame.Rect(1600, 600, 200, 20)], "bounce_blocks": [pygame.Rect(1900, 750, 50, 20)], "goal": pygame.Rect(2250, 300, 60, 60) }, { "width": 1600, "height": 1000, "start": (800, 900), "platforms": [pygame.Rect(0, 980, 1600, 20), pygame.Rect(0, 0, 1600, 20), pygame.Rect(0, 0, 20, 1000), pygame.Rect(1580, 0, 20, 1000)], "bounce_blocks": [pygame.Rect(100, 800, 60, 20), pygame.Rect(1440, 800, 60, 20), pygame.Rect(770, 600, 60, 20)], "goal": pygame.Rect(770, 100, 60, 60), "bosses": [ {"rect": pygame.Rect(200, 200, 120, 120), "vx": 6, "vy": -5, "start_x": 200, "start_y": 200}, {"rect": pygame.Rect(1200, 200, 120, 120), "vx": -5, "vy": -6, "start_x": 1200, "start_y": 200} ] }, # USER SUBMITTED LEVEL { 'width': 2000, 'height': 1200, 'start': (75, 475), 'platforms': [pygame.Rect(50, 500, 50, 20), pygame.Rect(100, 500, 50, 20), pygame.Rect(150, 500, 50, 20), pygame.Rect(200, 450, 50, 20), pygame.Rect(200, 450, 50, 20), pygame.Rect(250, 400, 50, 20), pygame.Rect(350, 400, 50, 20), pygame.Rect(300, 400, 50, 20), pygame.Rect(400, 450, 50, 20), pygame.Rect(400, 500, 50, 20), pygame.Rect(450, 500, 50, 20), pygame.Rect(550, 500, 50, 20), pygame.Rect(500, 500, 50, 20), pygame.Rect(600, 500, 50, 20), pygame.Rect(650, 500, 50, 20)], 'spikes': [pygame.Rect(400, 450, 50, 50), pygame.Rect(450, 450, 50, 50), pygame.Rect(500, 450, 50, 50), pygame.Rect(550, 450, 50, 50), pygame.Rect(600, 450, 50, 50), pygame.Rect(650, 450, 50, 50), pygame.Rect(450, 500, 50, 50), pygame.Rect(600, 500, 50, 50), pygame.Rect(650, 50, 50, 50), pygame.Rect(800, 50, 50, 50), pygame.Rect(900, 50, 50, 50), pygame.Rect(400, 50, 50, 50), pygame.Rect(750, -50, 50, 50)], 'ice_blocks': [pygame.Rect(750, 400, 50, 50), pygame.Rect(800, 400, 50, 50), pygame.Rect(850, 400, 50, 50), pygame.Rect(700, 500, 50, 50)], 'mud_blocks': [pygame.Rect(750, 300, 50, 50), pygame.Rect(650, 300, 50, 50), pygame.Rect(350, 200, 50, 50), pygame.Rect(550, 200, 50, 50), pygame.Rect(750, 150, 50, 50), pygame.Rect(550, 50, 50, 50), pygame.Rect(300, 100, 50, 50), pygame.Rect(350, -50, 50, 50), pygame.Rect(600, -50, 50, 50), pygame.Rect(500, 100, 50, 50)], 'conveyor_right': [pygame.Rect(950, 350, 50, 20), pygame.Rect(1000, 350, 50, 20), pygame.Rect(1050, 350, 50, 20), pygame.Rect(900, 500, 50, 20), pygame.Rect(1250, 500, 50, 20)], 'goal': pygame.Rect(700, -250, 50, 50) } ] def get_empty_editor_level(): return { "width": 2000, "height": 1200, "start": (100, 1000), "platforms": [], "spikes": [], "ice_blocks": [], "mud_blocks": [], "bounce_blocks": [], "jump_pads": [], "fake_walls": [], "conveyor_right": [], "conveyor_left": [], "coins": [], "keys": [], "doors": [], "goal": pygame.Rect(1800, 1000, 60, 60), "bosses": [] } editor_level = get_empty_editor_level() # --- TEXTURE GENERATION --- def create_stone_texture(w=50, h=50): surf = pygame.Surface((w, h)) surf.fill((80, 85, 95)) pygame.draw.rect(surf, (60, 65, 75), (0, 0, w, h), 2) pygame.draw.line(surf, (60, 65, 75), (0, h//2), (w, h//2), 2) pygame.draw.line(surf, (60, 65, 75), (w//2, 0), (w//2, h//2), 2) pygame.draw.line(surf, (60, 65, 75), (w//4, h//2), (w//4, h), 2) pygame.draw.line(surf, (60, 65, 75), (w*3//4, h//2), (w*3//4, h), 2) return surf def create_bounce_texture(w=50, h=50): surf = pygame.Surface((w, h)) surf.fill((40, 180, 60)) pygame.draw.rect(surf, (20, 100, 30), (0, 0, w, h), 3) pygame.draw.polygon(surf, (150, 255, 150), [(w//2, 10), (15, h-15), (w-15, h-15)]) return surf def create_pad_texture(w=50, h=50): surf = pygame.Surface((w, h)) surf.fill((0, 150, 200)) pygame.draw.rect(surf, (0, 100, 150), (0, 0, w, h), 3) pygame.draw.circle(surf, (0, 255, 255), (w//2, h//2), 15, 4) pygame.draw.circle(surf, (255, 255, 255), (w//2, h//2), 6) return surf def create_ice_texture(w=50, h=50): surf = pygame.Surface((w, h), pygame.SRCALPHA) surf.fill((120, 200, 255, 180)) pygame.draw.rect(surf, (180, 230, 255, 200), (0, 0, w, h), 2) pygame.draw.line(surf, (255, 255, 255, 220), (10, h-10), (w-10, 10), 3) pygame.draw.line(surf, (255, 255, 255, 150), (25, h-5), (w-5, 25), 2) return surf def create_mud_texture(w=50, h=50): surf = pygame.Surface((w, h)) surf.fill((100, 60, 30)) pygame.draw.rect(surf, (70, 40, 20), (0, 0, w, h), 2) pygame.draw.circle(surf, (70, 40, 20), (15, 15), 8) pygame.draw.circle(surf, (80, 50, 25), (35, 30), 10) pygame.draw.circle(surf, (60, 30, 15), (20, 40), 6) return surf def create_wood_texture(w=50, h=50): surf = pygame.Surface((w, h)) surf.fill((139, 69, 19)) for i in range(5, w, 15): pygame.draw.line(surf, (100, 40, 10), (i, 0), (i, h), 2) pygame.draw.circle(surf, (50, 20, 5), (w - 15, h // 2), 5) pygame.draw.rect(surf, (100, 40, 10), (0, 0, w, h), 3) return surf tex_stone = create_stone_texture() tex_bounce = create_bounce_texture() tex_pad = create_pad_texture() tex_ice = create_ice_texture() tex_mud = create_mud_texture() tex_door = create_wood_texture() def draw_tiled(surface, texture, rect): tw, th = texture.get_size() for x in range(rect.x, rect.right, tw): for y in range(rect.y, rect.bottom, th): blit_w = min(tw, rect.right - x) blit_h = min(th, rect.bottom - y) surface.blit(texture, (x, y), (0, 0, blit_w, blit_h)) def draw_beveled_rect(surface, color, rect, border_radius=0): pygame.draw.rect(surface, color, rect, border_radius=border_radius) light_color = (min(color[0]+60, 255), min(color[1]+60, 255), min(color[2]+60, 255)) pygame.draw.line(surface, light_color, rect.topleft, rect.topright, 2) pygame.draw.line(surface, light_color, rect.topleft, rect.bottomleft, 2) dark_color = (max(color[0]-60, 0), max(color[1]-60, 0), max(color[2]-60, 0)) pygame.draw.line(surface, dark_color, rect.bottomleft, rect.bottomright, 2) pygame.draw.line(surface, dark_color, rect.topright, rect.bottomright, 2) def create_sam_surface(): surf = pygame.Surface((BALL_RADIUS * 2, BALL_RADIUS * 2), pygame.SRCALPHA) cx, cy = BALL_RADIUS, BALL_RADIUS for r in range(BALL_RADIUS, 0, -1): color = (0, int(150 + (1-r/BALL_RADIUS)*100), 255) pygame.draw.circle(surf, color, (cx, cy), r) pygame.draw.circle(surf, (100, 200, 255, 150), (cx - 5, cy - 5), BALL_RADIUS // 2) pygame.draw.circle(surf, (255, 255, 255), (cx - 7, cy - 5), 6) pygame.draw.circle(surf, (255, 255, 255), (cx + 7, cy - 5), 6) pygame.draw.circle(surf, (0, 0, 0), (cx - 7, cy - 5), 3) pygame.draw.circle(surf, (0, 0, 0), (cx + 7, cy - 5), 3) pygame.draw.arc(surf, (0, 0, 0), pygame.Rect(cx - 10, cy - 2, 20, 12), math.pi, 0, 3) return surf sam_sprite = create_sam_surface() async def main(): global editor_level state = "INTRO" intro_start_time = pygame.time.get_ticks() current_level = 0 running = True ball_x, ball_y, vel_y, vel_x, rotation, jumps_left = 0, 0, 0, 0, 0, 2 current_accel, current_max_speed, current_friction = DEFAULT_ACCEL, DEFAULT_MAX_SPEED, DEFAULT_FRICTION # Editor Variables editor_cam_x, editor_cam_y = 0, 0 tools = ["start_pos", "platforms", "spikes", "ice_blocks", "mud_blocks", "bounce_blocks", "jump_pads", "fake_walls", "conveyor_right", "conveyor_left", "goal", "boss", "coins", "keys", "doors"] current_tool_idx = 1 grid_size = 50 upload_status = "" # Input Box Variables input_mode = None # Can be "NAME" or "AUTHOR" level_name_input = "" author_name_input = "" # Browser Variables online_levels_data = [] fetching_levels = False # New Runtime Variables score = 0 keys_held = 0 trail = [] active_coins = [] active_keys = [] active_doors = [] def load_level(level_data): nonlocal score, keys_held, trail, active_coins, active_keys, active_doors score = 0 keys_held = 0 trail = [] active_coins = [pygame.Rect(r) for r in level_data.get("coins", [])] active_keys = [pygame.Rect(r) for r in level_data.get("keys", [])] active_doors = [pygame.Rect(r) for r in level_data.get("doors", [])] start_pos = level_data["start"] for b in level_data.get("bosses", []): b["rect"].x, b["rect"].y = b["start_x"], b["start_y"] return start_pos[0], start_pos[1], 0, 0, 2 while running: time_ms = pygame.time.get_ticks() for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN: # Handle Text Input Overlay if input_mode: if event.key == pygame.K_ESCAPE: input_mode = None # Cancel upload upload_status = "Upload Cancelled." elif event.key == pygame.K_RETURN: if input_mode == "NAME": input_mode = "AUTHOR" elif input_mode == "AUTHOR": # Done typing! Trigger Upload! input_mode = None upload_status = "Uploading..." async def upload_task(lvl_name, auth_name): nonlocal upload_status payload = serialize_level(editor_level, name=lvl_name, author=auth_name) success = False if sys.version_info >= (3, 9): success = await asyncio.to_thread(_do_upload, payload) upload_status = "Upload Success!" if success else "Upload Failed." asyncio.create_task(upload_task(level_name_input, author_name_input)) elif event.key == pygame.K_BACKSPACE: if input_mode == "NAME": level_name_input = level_name_input[:-1] else: author_name_input = author_name_input[:-1] else: if len(event.unicode) > 0 and event.unicode.isprintable(): if input_mode == "NAME" and len(level_name_input) < 20: level_name_input += event.unicode elif input_mode == "AUTHOR" and len(author_name_input) < 15: author_name_input += event.unicode continue # Skip other keyboard processing while typing if state in ["MENU", "WIN"]: if event.key == pygame.K_SPACE: state = "PLAY" current_level = 0 ball_x, ball_y, vel_x, vel_y, jumps_left = load_level(levels[current_level]) elif event.key == pygame.K_e: state = "EDITOR" elif event.key == pygame.K_o: state = "BROWSER" fetching_levels = True async def fetch_and_set(): nonlocal online_levels_data, fetching_levels if sys.version_info >= (3, 9): online_levels_data = await asyncio.to_thread(_do_fetch) fetching_levels = False asyncio.create_task(fetch_and_set()) elif state == "BROWSER": if event.key == pygame.K_ESCAPE: state = "MENU" elif state == "PLAY": if event.key == pygame.K_UP and jumps_left > 0: vel_y = JUMP_SPEED; jumps_left -= 1 if event.key == pygame.K_ESCAPE and current_level == -1: state = "EDITOR" elif state == "EDITOR": if event.key == pygame.K_SPACE: state = "PLAY" current_level = -1 ball_x, ball_y, vel_x, vel_y, jumps_left = load_level(editor_level) if event.key == pygame.K_LEFT: current_tool_idx = max(0, current_tool_idx - 1) if event.key == pygame.K_RIGHT: current_tool_idx = min(len(tools) - 1, current_tool_idx + 1) if event.key == pygame.K_c: # CLEAR EDITOR editor_level = get_empty_editor_level() if event.key == pygame.K_u: # START UPLOAD PROCESS input_mode = "NAME" level_name_input = "" author_name_input = "" if event.type == pygame.MOUSEBUTTONDOWN and not input_mode: mx, my = pygame.mouse.get_pos() if state == "BROWSER" and not fetching_levels: # Click to play online level for i, ml in enumerate(online_levels_data): y_pos = 150 + (i * 100) btn_rect = pygame.Rect(WIDTH - 200, y_pos + 15, 120, 50) if btn_rect.collidepoint(mx, my): state = "PLAY" current_level = -1 editor_level = deserialize_level(ml) ball_x, ball_y, vel_x, vel_y, jumps_left = load_level(editor_level) elif state == "EDITOR": if my > HEIGHT - 50: tool_width = WIDTH // len(tools) clicked_idx = mx // tool_width if clicked_idx < len(tools): current_tool_idx = clicked_idx continue world_x, world_y = mx + editor_cam_x, my + editor_cam_y snap_x, snap_y = (world_x // grid_size) * grid_size, (world_y // grid_size) * grid_size new_rect = pygame.Rect(snap_x, snap_y, grid_size, grid_size) tool_name = tools[current_tool_idx] if event.button == 1: if tool_name == "goal": editor_level["goal"] = new_rect elif tool_name == "start_pos": editor_level["start"] = (snap_x + grid_size//2, snap_y + grid_size//2) elif tool_name == "boss": editor_level["bosses"].append({"rect": pygame.Rect(snap_x, snap_y, 100, 100), "vx": 5, "vy": -5, "start_x": snap_x, "start_y": snap_y}) else: if tool_name in ["platforms", "conveyor_right", "conveyor_left", "doors"]: new_rect.height = 20 elif tool_name in ["coins", "keys"]: new_rect = pygame.Rect(snap_x + 10, snap_y + 10, 30, 30) editor_level[tool_name].append(new_rect) elif event.button == 3: for key in editor_level: if isinstance(editor_level[key], list): if key == "bosses": editor_level[key] = [b for b in editor_level[key] if not b["rect"].collidepoint(world_x, world_y)] else: editor_level[key] = [r for r in editor_level[key] if not r.collidepoint(world_x, world_y)] if state == "INTRO": screen.fill((5, 5, 15)) pulse = math.sin(time_ms / 200.0) * 50 intro_text = font_intro.render("Plazma Studios", True, (0, 200 + pulse, 150 + pulse)) screen.blit(intro_text, (WIDTH // 2 - intro_text.get_width() // 2, HEIGHT // 2 - intro_text.get_height() // 2)) if time_ms - intro_start_time > 3000: state = "MENU" elif state == "MENU": screen.fill((20, 20, 40)) title = font_title.render("CIRCLE SAM", True, (255, 255, 255)) screen.blit(title, (WIDTH // 2 - title.get_width() // 2, HEIGHT // 3 - 50)) screen.blit(font_prompt.render("Press SPACE to Play Campaign", True, (100, 255, 100)), (WIDTH // 2 - 180, HEIGHT // 2)) screen.blit(font_prompt.render("Press 'E' for Level Editor", True, (255, 200, 100)), (WIDTH // 2 - 150, HEIGHT // 2 + 50)) screen.blit(font_prompt.render("Press 'O' for Online Browser", True, (100, 200, 255)), (WIDTH // 2 - 160, HEIGHT // 2 + 100)) elif state == "BROWSER": screen.fill((25, 30, 45)) screen.blit(font_title.render("COMMUNITY LEVELS", True, (255, 255, 255)), (20, 20)) screen.blit(font_prompt.render("Press ESC to return to Menu", True, (150, 150, 150)), (20, 80)) if fetching_levels: screen.blit(font_prompt.render("Fetching levels from Back4App...", True, (255, 255, 0)), (50, 200)) elif not online_levels_data: screen.blit(font_prompt.render("No levels found or error connecting.", True, (255, 100, 100)), (50, 200)) else: for i, ml in enumerate(online_levels_data[:4]): # Show up to 4 y_pos = 150 + (i * 100) pygame.draw.rect(screen, (40, 45, 65), (50, y_pos, WIDTH - 100, 80), border_radius=10) screen.blit(font_prompt.render(ml.get("name", "Unknown Level"), True, (255, 200, 100)), (70, y_pos + 15)) screen.blit(font_browser.render(f"By: {ml.get('author', 'Anonymous')}", True, (200, 200, 200)), (70, y_pos + 45)) pygame.draw.rect(screen, (100, 255, 100), (WIDTH - 200, y_pos + 15, 120, 50), border_radius=5) screen.blit(font_browser.render("PLAY", True, (0, 100, 0)), (WIDTH - 170, y_pos + 30)) elif state == "EDITOR" or state == "PLAY": level_data = editor_level if (state == "EDITOR" or current_level == -1) else levels[current_level] lvl_width, lvl_height = level_data["width"], level_data["height"] if state == "PLAY": keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: vel_x -= current_accel if keys[pygame.K_RIGHT]: vel_x += current_accel vel_x *= current_friction if vel_x > current_max_speed: vel_x = current_max_speed if vel_x < -current_max_speed: vel_x = -current_max_speed vel_y += GRAVITY; ball_y += vel_y; ball_x += vel_x if vel_x != 0: rotation = (rotation - vel_x * 2.5) % 360 ball_rect = pygame.Rect(ball_x - BALL_RADIUS, ball_y - BALL_RADIUS, BALL_RADIUS * 2, BALL_RADIUS * 2) current_accel, current_max_speed, current_friction = DEFAULT_ACCEL, DEFAULT_MAX_SPEED, DEFAULT_FRICTION if ball_x < BALL_RADIUS: ball_x = BALL_RADIUS; vel_x *= WALL_BOUNCE if ball_x > lvl_width - BALL_RADIUS: ball_x = lvl_width - BALL_RADIUS; vel_x *= WALL_BOUNCE if ball_y >= lvl_height - BALL_RADIUS: ball_y = lvl_height - BALL_RADIUS; vel_y = vel_y * BOUNCE if vel_y > 3 else 0; jumps_left = 2 for spike in level_data.get("spikes", []): if ball_rect.colliderect(spike): ball_x, ball_y, vel_x, vel_y, jumps_left = load_level(level_data) for plat in level_data.get("platforms", []): if ball_rect.colliderect(plat) and vel_y >= 0 and ball_y < plat.centery: ball_y = plat.top - BALL_RADIUS; vel_y = vel_y * BOUNCE if vel_y > 3 else 0; jumps_left = 2 for block in level_data.get("bounce_blocks", []): if ball_rect.colliderect(block) and vel_y >= 0 and ball_y < block.centery: ball_y = block.top - BALL_RADIUS; vel_y = SUPER_BOUNCE; jumps_left = 2 for pad in level_data.get("jump_pads", []): if ball_rect.colliderect(pad) and vel_y >= 0 and ball_y < pad.centery: ball_y = pad.top - BALL_RADIUS; vel_y = MEGA_BOUNCE; jumps_left = 2 for ice in level_data.get("ice_blocks", []): if ball_rect.colliderect(ice) and vel_y >= 0 and ball_y < ice.centery: ball_y = ice.top - BALL_RADIUS; vel_y = vel_y * BOUNCE if vel_y > 3 else 0; jumps_left = 2; current_friction = 0.99 for mud in level_data.get("mud_blocks", []): if ball_rect.colliderect(mud) and vel_y >= 0 and ball_y < mud.centery: ball_y = mud.top - BALL_RADIUS; vel_y, jumps_left = 0, 2; current_friction, current_max_speed, current_accel = 0.80, 3.5, 0.3 for conv in level_data.get("conveyor_right", []): if ball_rect.colliderect(conv) and vel_y >= 0 and ball_y < conv.centery: ball_y = conv.top - BALL_RADIUS; vel_y = vel_y * BOUNCE if vel_y > 3 else 0; jumps_left = 2; vel_x += 1.5 for conv in level_data.get("conveyor_left", []): if ball_rect.colliderect(conv) and vel_y >= 0 and ball_y < conv.centery: ball_y = conv.top - BALL_RADIUS; vel_y = vel_y * BOUNCE if vel_y > 3 else 0; jumps_left = 2; vel_x -= 1.5 for boss in level_data.get("bosses", []): b_rect = boss["rect"]; b_rect.x += boss["vx"]; b_rect.y += boss["vy"] if b_rect.centerx < ball_x: b_rect.x += 1.5 if b_rect.centerx > ball_x: b_rect.x -= 1.5 if b_rect.left < 0 or b_rect.right > lvl_width: boss["vx"] *= -1 if b_rect.top < 0 or b_rect.bottom > lvl_height: boss["vy"] *= -1 if ball_rect.colliderect(b_rect): ball_x, ball_y, vel_x, vel_y, jumps_left = load_level(level_data) for coin in active_coins[:]: if ball_rect.colliderect(coin): active_coins.remove(coin) score += 10 for key_rect in active_keys[:]: if ball_rect.colliderect(key_rect): active_keys.remove(key_rect) keys_held += 1 for door in active_doors[:]: if ball_rect.colliderect(door): if keys_held > 0: active_doors.remove(door) keys_held -= 1 elif vel_y >= 0 and ball_y < door.centery: ball_y = door.top - BALL_RADIUS; vel_y = vel_y * BOUNCE if vel_y > 3 else 0; jumps_left = 2 if ball_rect.colliderect(level_data["goal"]): if current_level == -1: state = "EDITOR" else: current_level += 1 if current_level >= len(levels): state = "WIN" else: ball_x, ball_y, vel_x, vel_y, jumps_left = load_level(levels[current_level]) camera_x = max(0, min(ball_x - WIDTH // 2, lvl_width - WIDTH)) camera_y = max(0, min(ball_y - HEIGHT // 2, lvl_height - HEIGHT)) trail.append((ball_x, ball_y)) if len(trail) > 15: trail.pop(0) else: # EDITOR if not input_mode: # Only move camera if not typing keys = pygame.key.get_pressed() if keys[pygame.K_w]: editor_cam_y -= 10 if keys[pygame.K_s]: editor_cam_y += 10 if keys[pygame.K_a]: editor_cam_x -= 10 if keys[pygame.K_d]: editor_cam_x += 10 camera_x, camera_y = editor_cam_x, editor_cam_y # DRAWING screen.fill((15, 20, 30) if state == "EDITOR" else (10, 15, 20)) grid_col = (25, 30, 45) if state == "EDITOR" else (15, 20, 30) for x in range(int(-camera_x % grid_size), WIDTH, grid_size): pygame.draw.line(screen, grid_col, (x, 0), (x, HEIGHT)) for y in range(int(-camera_y % grid_size), HEIGHT, grid_size): pygame.draw.line(screen, grid_col, (0, y), (WIDTH, y)) def apply_cam(rect): return pygame.Rect(rect.x - camera_x, rect.y - camera_y, rect.width, rect.height) for plat in level_data.get("platforms", []): draw_tiled(screen, tex_stone, apply_cam(plat)) for block in level_data.get("bounce_blocks", []): draw_tiled(screen, tex_bounce, apply_cam(block)) for pad in level_data.get("jump_pads", []): draw_tiled(screen, tex_pad, apply_cam(pad)) for ice in level_data.get("ice_blocks", []): draw_tiled(screen, tex_ice, apply_cam(ice)) for mud in level_data.get("mud_blocks", []): draw_tiled(screen, tex_mud, apply_cam(mud)) for wall in level_data.get("fake_walls", []): cam_wall = apply_cam(wall) draw_tiled(screen, tex_stone, cam_wall) shadow = pygame.Surface((cam_wall.width, cam_wall.height), pygame.SRCALPHA) shadow.fill((0, 0, 0, 120 if state == "EDITOR" else 50)) screen.blit(shadow, cam_wall.topleft) conveyor_offset = (time_ms // 20) % 20 for conv in level_data.get("conveyor_right", []): cam_conv = apply_cam(conv) pygame.draw.rect(screen, (100, 100, 100), cam_conv) pygame.draw.rect(screen, (150, 150, 150), cam_conv, 3) for i in range(0, conv.width, 20): px = cam_conv.left + ((i + conveyor_offset) % cam_conv.width) pygame.draw.line(screen, (255, 200, 0), (px, cam_conv.top + 4), (px + 6, cam_conv.centery), 3) pygame.draw.line(screen, (255, 200, 0), (px + 6, cam_conv.centery), (px, cam_conv.bottom - 4), 3) for conv in level_data.get("conveyor_left", []): cam_conv = apply_cam(conv) pygame.draw.rect(screen, (100, 100, 100), cam_conv) pygame.draw.rect(screen, (150, 150, 150), cam_conv, 3) for i in range(0, conv.width, 20): px = cam_conv.left + ((i - conveyor_offset) % cam_conv.width) pygame.draw.line(screen, (255, 140, 0), (px, cam_conv.top + 4), (px - 6, cam_conv.centery), 3) pygame.draw.line(screen, (255, 140, 0), (px - 6, cam_conv.centery), (px, cam_conv.bottom - 4), 3) for spike in level_data.get("spikes", []): cam_spike = apply_cam(spike) for i in range(0, cam_spike.width, 25): left_x, right_x = cam_spike.left + i, min(cam_spike.left + i + 25, cam_spike.right) center_x = (left_x + right_x) / 2 pygame.draw.polygon(screen, (200, 50, 50), [(left_x, cam_spike.bottom), (right_x, cam_spike.bottom), (center_x, cam_spike.top)]) pygame.draw.polygon(screen, (255, 100, 100), [(left_x, cam_spike.bottom), (center_x, cam_spike.bottom), (center_x, cam_spike.top)]) pygame.draw.line(screen, (150, 20, 20), (left_x, cam_spike.bottom), (center_x, cam_spike.top), 2) pygame.draw.line(screen, (150, 20, 20), (right_x, cam_spike.bottom), (center_x, cam_spike.top), 2) coins_to_draw = active_coins if state == "PLAY" else level_data.get("coins", []) keys_to_draw = active_keys if state == "PLAY" else level_data.get("keys", []) doors_to_draw = active_doors if state == "PLAY" else level_data.get("doors", []) pulse_coin = math.sin(time_ms / 100.0) * 3 for coin in coins_to_draw: cx, cy = int(coin.centerx - camera_x), int(coin.centery - camera_y) pygame.draw.circle(screen, (255, 200, 0), (cx, cy), int(12 + pulse_coin)) pygame.draw.circle(screen, (255, 255, 100), (cx, cy), int(8 + pulse_coin)) pygame.draw.circle(screen, (255, 255, 255), (cx - 4, cy - 4), 3) for key_rect in keys_to_draw: kx, ky = int(key_rect.centerx - camera_x), int(key_rect.centery - camera_y) pygame.draw.rect(screen, (200, 200, 200), (kx - 10, ky - 3, 20, 6), border_radius=3) pygame.draw.circle(screen, (255, 200, 50), (kx - 10, ky), 7) pygame.draw.circle(screen, (25, 30, 40), (kx - 10, ky), 3) pygame.draw.rect(screen, (255, 200, 50), (kx + 4, ky - 3, 4, 8)) pygame.draw.rect(screen, (255, 200, 50), (kx + 10, ky - 3, 4, 8)) for door in doors_to_draw: draw_tiled(screen, tex_door, apply_cam(door)) for boss in level_data.get("bosses", []): b_rect = apply_cam(boss["rect"]) pygame.draw.rect(screen, (255, 0, 50), b_rect); pygame.draw.rect(screen, (0, 0, 0), b_rect, 4) pygame.draw.rect(screen, (0, 0, 0), (b_rect.x + (b_rect.width//5), b_rect.y + (b_rect.height//4), b_rect.width//5, b_rect.height//7)) pygame.draw.rect(screen, (0, 0, 0), (b_rect.right - (b_rect.width//2.5), b_rect.y + (b_rect.height//4), b_rect.width//5, b_rect.height//7)) pygame.draw.line(screen, (0,0,0), (b_rect.x + (b_rect.width//4), b_rect.bottom - (b_rect.height//4)), (b_rect.right - (b_rect.width//4), b_rect.bottom - (b_rect.height//4)), 5) pulse = math.sin(time_ms / 150.0) * 8 g_rect = level_data["goal"] gcx, gcy = int(g_rect.centerx - camera_x), int(g_rect.centery - camera_y) pygame.draw.circle(screen, (100, 0, 255), (gcx, gcy), int(25 + pulse), 3) pygame.draw.circle(screen, (150, 50, 255), (gcx, gcy), int(18 - pulse / 2)) pygame.draw.circle(screen, (255, 255, 255), (gcx, gcy), 8) if state == "EDITOR": sx, sy = editor_level["start"] pygame.draw.circle(screen, (0, 255, 0), (int(sx - camera_x), int(sy - camera_y)), 10) if not HAS_REQUESTS: screen.blit(font_editor.render(f"WARNING: pip install requests to fix upload!", True, (255, 100, 100)), (10, 10)) else: screen.blit(font_editor.render(f"U: Upload | C: Clear | Status: {upload_status}", True, (255, 255, 255)), (10, 10)) # Toolbar pygame.draw.rect(screen, (20, 20, 20), (0, HEIGHT - 50, WIDTH, 50)) tool_width = WIDTH // len(tools) for i, tool in enumerate(tools): rect = pygame.Rect(i * tool_width, HEIGHT - 50, tool_width, 50) color = (80, 80, 100) if i == current_tool_idx else (40, 40, 50) pygame.draw.rect(screen, color, rect) pygame.draw.rect(screen, (100, 100, 100), rect, 1) screen.blit(font_editor.render(str(i), True, (200, 200, 200)), (rect.x + 5, rect.y + 5)) name_lbl = font_editor.render(tool[:4].upper(), True, (255, 255, 255)) screen.blit(name_lbl, (rect.centerx - name_lbl.get_width()//2, rect.centery - name_lbl.get_height()//2 + 5)) # Input Box Overlay if input_mode: overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA) overlay.fill((0, 0, 0, 180)) screen.blit(overlay, (0, 0)) box = pygame.Rect(WIDTH//2 - 200, HEIGHT//2 - 100, 400, 200) pygame.draw.rect(screen, (40, 45, 65), box, border_radius=10) pygame.draw.rect(screen, (255, 255, 255), box, 2, border_radius=10) if input_mode == "NAME": prompt = font_prompt.render("Level Name:", True, (255, 200, 100)) text_surface = font_prompt.render(level_name_input + ( "_" if (time_ms % 1000) < 500 else ""), True, (255, 255, 255)) else: prompt = font_prompt.render("Author Name:", True, (255, 200, 100)) text_surface = font_prompt.render(author_name_input + ( "_" if (time_ms % 1000) < 500 else ""), True, (255, 255, 255)) screen.blit(prompt, (box.x + 20, box.y + 30)) screen.blit(text_surface, (box.x + 20, box.y + 80)) screen.blit(font_editor.render("Press ENTER to confirm, ESC to cancel.", True, (150, 150, 150)), (box.x + 20, box.y + 150)) elif state == "PLAY": # Draw Trail for i, (tx, ty) in enumerate(trail): radius = int(BALL_RADIUS * (i / len(trail)) * 0.6) pygame.draw.circle(screen, (0, 150, 255), (int(tx - camera_x), int(ty - camera_y)), radius) rot_sam = pygame.transform.rotate(sam_sprite, rotation) screen.blit(rot_sam, rot_sam.get_rect(center=(int(ball_x - camera_x), int(ball_y - camera_y))).topleft) # UI Text screen.blit(font_browser.render(f"SCORE: {score}", True, (255, 215, 0)), (10, 10)) screen.blit(font_browser.render(f"KEYS: {keys_held}", True, (200, 200, 200)), (10, 40)) pygame.display.flip() clock.tick(60) await asyncio.sleep(0) pygame.quit() sys.exit() asyncio.ensure_future(main())