crystalwindow 1.4.7.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,77 @@
1
+ import os
2
+ import json
3
+ import pickle
4
+ import tkinter as tk
5
+ from tkinter import filedialog
6
+
7
+ class FileHelper:
8
+ """CrystalWindow integrated file helper with default folders & Tk dialogs"""
9
+
10
+ def __init__(self, default_save_folder="saves"):
11
+ self.default_save_folder = default_save_folder
12
+ os.makedirs(self.default_save_folder, exist_ok=True)
13
+
14
+ # ---- TK FILE DIALOGS ----
15
+ def ask_save_file(self, default_name="save.json", filetypes=[("JSON files","*.json"),("All files","*.*")]):
16
+ root = tk.Tk()
17
+ root.withdraw()
18
+ path = filedialog.asksaveasfilename(
19
+ initialdir=self.default_save_folder,
20
+ initialfile=default_name,
21
+ filetypes=filetypes,
22
+ defaultextension=filetypes[0][1]
23
+ )
24
+ root.destroy()
25
+ return path
26
+
27
+ def ask_open_file(self, filetypes=[("JSON files","*.json"),("All files","*.*")]):
28
+ root = tk.Tk()
29
+ root.withdraw()
30
+ path = filedialog.askopenfilename(
31
+ initialdir=self.default_save_folder,
32
+ filetypes=filetypes
33
+ )
34
+ root.destroy()
35
+ return path
36
+
37
+ # ---- TEXT ----
38
+ def save_text(self, filename, content):
39
+ path = os.path.join(self.default_save_folder, filename)
40
+ with open(path, "w", encoding="utf-8") as f:
41
+ f.write(content)
42
+ return path
43
+
44
+ def load_text(self, filename):
45
+ path = os.path.join(self.default_save_folder, filename)
46
+ if os.path.exists(path):
47
+ with open(path, "r", encoding="utf-8") as f:
48
+ return f.read()
49
+ return None
50
+
51
+ # ---- JSON ----
52
+ def save_json(self, filename, data):
53
+ path = os.path.join(self.default_save_folder, filename)
54
+ with open(path, "w", encoding="utf-8") as f:
55
+ json.dump(data, f, indent=4)
56
+ return path
57
+
58
+ def load_json(self, filename):
59
+ path = os.path.join(self.default_save_folder, filename)
60
+ if os.path.exists(path):
61
+ with open(path, "r", encoding="utf-8") as f:
62
+ return json.load(f)
63
+ return None
64
+
65
+ # ---- PICKLE ----
66
+ def save_pickle(self, filename, obj):
67
+ path = os.path.join(self.default_save_folder, filename)
68
+ with open(path, "wb") as f:
69
+ pickle.dump(obj, f)
70
+ return path
71
+
72
+ def load_pickle(self, filename):
73
+ path = os.path.join(self.default_save_folder, filename)
74
+ if os.path.exists(path):
75
+ with open(path, "rb") as f:
76
+ return pickle.load(f)
77
+ return None
@@ -0,0 +1,15 @@
1
+ from .window import Window
2
+ from .sprites import Sprite
3
+ from .tilemap import TileMap
4
+ from .assets import load_image, load_folder_images, load_music, play_music
5
+ from .animation import Animation
6
+ from .collision import check_collision, resolve_collision
7
+ from .player import Player
8
+ from .gui import Button, Label, GUIManager, random_color, hex_to_rgb
9
+ from .gui_ext import Toggle, Slider
10
+ from .fun_helpers import random_name, DebugOverlay
11
+ from .draw_helpers import gradient_rect, CameraShake
12
+ from .draw_text_helper import DrawTextManager
13
+ from .gravity import Gravity
14
+ from .draw_rects import DrawHelper
15
+ from .FileHelper import FileHelper
@@ -0,0 +1,15 @@
1
+ class Animation:
2
+ def __init__(self, frames, speed=0.1):
3
+ self.frames = frames
4
+ self.speed = speed
5
+ self.current = 0
6
+ self.timer = 0
7
+
8
+ def update(self, dt):
9
+ self.timer += dt
10
+ if self.timer >= self.speed:
11
+ self.timer = 0
12
+ self.current = (self.current + 1) % len(self.frames)
13
+
14
+ def get_frame(self):
15
+ return self.frames[self.current]
@@ -0,0 +1,33 @@
1
+ import pygame
2
+ import os
3
+
4
+ ASSETS = {}
5
+
6
+ def load_image(path, size=None):
7
+ if not os.path.exists(path):
8
+ raise FileNotFoundError(f"Image not found: {path}")
9
+ img = pygame.image.load(path).convert_alpha()
10
+ if size:
11
+ img = pygame.transform.scale(img, size)
12
+ ASSETS[path] = img
13
+ return img
14
+
15
+ def load_folder_images(folder, size=None, nested=True):
16
+ result = {}
17
+ for item in os.listdir(folder):
18
+ full_path = os.path.join(folder, item)
19
+ if os.path.isdir(full_path) and nested:
20
+ result[item] = load_folder_images(full_path, size=size, nested=True)
21
+ elif item.lower().endswith((".png", ".jpg", ".jpeg")):
22
+ result[item] = load_image(full_path, size=size)
23
+ return result
24
+
25
+ def load_music(path):
26
+ if not os.path.exists(path):
27
+ raise FileNotFoundError(f"Music not found: {path}")
28
+ pygame.mixer.music.load(path)
29
+ ASSETS[path] = path
30
+ return path
31
+
32
+ def play_music(loop=-1):
33
+ pygame.mixer.music.play(loop)
@@ -0,0 +1,15 @@
1
+ def check_collision(sprite, tiles):
2
+ for t in tiles:
3
+ if sprite.rect.colliderect(t.rect):
4
+ return True
5
+ return False
6
+
7
+ def resolve_collision(sprite, tiles, dy):
8
+ sprite.rect.y += dy
9
+ collided = [t for t in tiles if sprite.rect.colliderect(t.rect)]
10
+ for t in collided:
11
+ if dy > 0:
12
+ sprite.rect.bottom = t.rect.top
13
+ elif dy < 0:
14
+ sprite.rect.top = t.rect.bottom
15
+ sprite.y = sprite.rect.y
@@ -0,0 +1,23 @@
1
+ from .window import Window
2
+
3
+ def gradient_rect(win, rect, color_start, color_end, vertical=True):
4
+ x,y,w,h = rect
5
+ for i in range(h if vertical else w):
6
+ t = i/(h if vertical else w)
7
+ r = int(color_start[0]*(1-t) + color_end[0]*t)
8
+ g = int(color_start[1]*(1-t) + color_end[1]*t)
9
+ b = int(color_start[2]*(1-t) + color_end[2]*t)
10
+ if vertical:
11
+ win.draw_rect((r,g,b), (x,y+i,w,1))
12
+ else:
13
+ win.draw_rect((r,g,b), (x+i,y,1,h))
14
+
15
+ class CameraShake:
16
+ def __init__(self, intensity=5):
17
+ self.intensity = intensity
18
+ self.offset = (0,0)
19
+
20
+ def update(self):
21
+ import random
22
+ self.offset = (random.randint(-self.intensity,self.intensity),
23
+ random.randint(-self.intensity,self.intensity))
@@ -0,0 +1,51 @@
1
+ import pygame as py
2
+ from crystalwindow import *
3
+
4
+ class DrawHelper:
5
+ def __init__(self):
6
+ pass
7
+
8
+ def rect(self, win, x, y, w, h, color):
9
+ py.draw.rect(win.screen, color, (x, y, w, h))
10
+ return self
11
+
12
+ def square(self, win, x, y, size, color):
13
+ py.draw.rect(win.screen, color, (x, y, size, size))
14
+ return self
15
+
16
+ def circle(self, win, x, y, radius, color):
17
+ py.draw.circle(win.screen, color, (x, y), radius)
18
+ return self
19
+
20
+ def triangle(self, win, points, color):
21
+ py.draw.polygon(win.screen, color, points)
22
+ return self
23
+
24
+ def texture(self, win, x, y, w, h, image):
25
+ if image:
26
+ img = py.transform.scale(image, (w, h))
27
+ win.screen.blit(img, (x, y))
28
+ return self
29
+
30
+ def text(self, win, text, font="Arial", size=16, x=0, y=0, color=(255,255,255), bold=False, cursive=False):
31
+ fnt = py.font.SysFont(font, size, bold=bold, italic=cursive)
32
+ surf = fnt.render(text, True, color)
33
+ win.screen.blit(surf, (x, y))
34
+ return self
35
+
36
+ def gradient_rect(self, win, x, y, w, h, start_color, end_color, vertical=True):
37
+ if vertical:
38
+ for i in range(h):
39
+ ratio = i / h
40
+ r = int(start_color[0]*(1-ratio) + end_color[0]*ratio)
41
+ g = int(start_color[1]*(1-ratio) + end_color[1]*ratio)
42
+ b = int(start_color[2]*(1-ratio) + end_color[2]*ratio)
43
+ py.draw.line(win.screen, (r, g, b), (x, y+i), (x+w, y+i))
44
+ else:
45
+ for i in range(w):
46
+ ratio = i / w
47
+ r = int(start_color[0]*(1-ratio) + end_color[0]*ratio)
48
+ g = int(start_color[1]*(1-ratio) + end_color[1]*ratio)
49
+ b = int(start_color[2]*(1-ratio) + end_color[2]*ratio)
50
+ py.draw.line(win.screen, (r, g, b), (x+i, y), (x+i, y+h))
51
+ return self
@@ -0,0 +1,45 @@
1
+ from .window import Window
2
+
3
+ class DrawTextManager:
4
+ def __init__(self):
5
+ self.texts = []
6
+
7
+ def add_text(self, text, pos=(0,0), size=16, color=(255,255,255), bold=False, italic=False, duration=None):
8
+ """
9
+ text: str
10
+ pos: (x,y)
11
+ size: int
12
+ color: RGB tuple
13
+ bold: bool
14
+ italic: bool
15
+ duration: float seconds or None for permanent
16
+ """
17
+ self.texts.append({
18
+ "text": text,
19
+ "pos": pos,
20
+ "size": size,
21
+ "color": color,
22
+ "bold": bold,
23
+ "italic": italic,
24
+ "duration": duration,
25
+ "timer": 0
26
+ })
27
+
28
+ def update(self, dt):
29
+ # update timers and remove expired text
30
+ for t in self.texts[:]:
31
+ if t["duration"] is not None:
32
+ t["timer"] += dt
33
+ if t["timer"] >= t["duration"]:
34
+ self.texts.remove(t)
35
+
36
+ def draw(self, win: Window):
37
+ for t in self.texts:
38
+ win.draw_text(
39
+ t["text"],
40
+ pos=t["pos"],
41
+ size=t["size"],
42
+ color=t["color"],
43
+ bold=t["bold"],
44
+ italic=t["italic"]
45
+ )
@@ -0,0 +1,28 @@
1
+ import random
2
+
3
+ # random color helpers
4
+ def random_color():
5
+ return (random.randint(0,255), random.randint(0,255), random.randint(0,255))
6
+
7
+ def random_palette(n=5):
8
+ return [random_color() for _ in range(n)]
9
+
10
+ # random name generator
11
+ def random_name():
12
+ syllables = ["ka","zi","lo","ra","mi","to","na","ve"]
13
+ return "".join(random.choice(syllables) for _ in range(3))
14
+
15
+ # tween helper
16
+ def lerp(a, b, t):
17
+ return a + (b - a) * t
18
+
19
+ # debug overlay
20
+ class DebugOverlay:
21
+ def __init__(self):
22
+ self.active = True
23
+
24
+ def draw(self, win, fps=60):
25
+ if self.active:
26
+ win.draw_text(f"FPS: {fps}", pos=(10,10))
27
+ mx,my = win.mouse_pos
28
+ win.draw_text(f"Mouse: {mx},{my}", pos=(10,30))
@@ -0,0 +1,75 @@
1
+ class Gravity:
2
+ def __init__(self, obj, force=1, terminal_velocity=15, bouncy=False, bounce_strength=0.7):
3
+ self.obj = obj
4
+ if not hasattr(self.obj, "vel_y"):
5
+ self.obj.vel_y = 0
6
+ self.force = force
7
+ self.terminal_velocity = terminal_velocity
8
+ self.bouncy = bouncy
9
+ self.bounce_strength = bounce_strength
10
+ self.on_ground = False
11
+ self.stretch_factor = 0 # for squishy effect
12
+
13
+ # --- NEW: choose mode ---
14
+ # If object has .sprite use that, else fallback to rect mode
15
+ if hasattr(obj, "sprite"):
16
+ self.mode = "sprite"
17
+ else:
18
+ self.mode = "rect"
19
+
20
+ def get_obj_rect(self):
21
+ # helper to get x,y,w,h depending on mode
22
+ if self.mode == "sprite":
23
+ s = self.obj.sprite
24
+ x = getattr(s, "x", 0)
25
+ y = getattr(s, "y", 0)
26
+ w = getattr(s, "width", getattr(s, "w", 32))
27
+ h = getattr(s, "height", getattr(s, "h", 32))
28
+ else:
29
+ x = getattr(self.obj, "x", 0)
30
+ y = getattr(self.obj, "y", 0)
31
+ w = getattr(self.obj, "width", getattr(self.obj, "w", 32))
32
+ h = getattr(self.obj, "height", getattr(self.obj, "h", 32))
33
+ return x, y, w, h
34
+
35
+ def update(self, dt, platforms=[]):
36
+ # apply gravity
37
+ self.obj.vel_y += self.force * dt * 60
38
+ if self.obj.vel_y > self.terminal_velocity:
39
+ self.obj.vel_y = self.terminal_velocity
40
+
41
+ # move object
42
+ x, y, w, h = self.get_obj_rect()
43
+ y += self.obj.vel_y
44
+ self.on_ground = False
45
+
46
+ # check collisions
47
+ for plat in platforms:
48
+ plat_w = getattr(plat, "width", getattr(plat, "w", 0))
49
+ plat_h = getattr(plat, "height", getattr(plat, "h", 0))
50
+ if (x + w > plat.x and x < plat.x + plat_w and
51
+ y + h > plat.y and y < plat.y + plat_h):
52
+
53
+ y = plat.y - h
54
+ self.on_ground = True
55
+
56
+ if self.bouncy:
57
+ self.obj.vel_y = -self.obj.vel_y * self.bounce_strength
58
+ self.stretch_factor = min(0.5, self.stretch_factor + 0.2)
59
+ else:
60
+ self.obj.vel_y = 0
61
+ self.stretch_factor = 0
62
+
63
+ # slowly reset stretch
64
+ if self.stretch_factor > 0:
65
+ self.stretch_factor -= 0.05
66
+ if self.stretch_factor < 0:
67
+ self.stretch_factor = 0
68
+
69
+ # write back position
70
+ if self.mode == "sprite":
71
+ self.obj.sprite.x = x
72
+ self.obj.sprite.y = y
73
+ else:
74
+ self.obj.x = x
75
+ self.obj.y = y
crystalwindow/gui.py ADDED
@@ -0,0 +1,64 @@
1
+ import pygame
2
+ import random
3
+
4
+ # ----------------- Color Helpers -----------------
5
+ def hex_to_rgb(hex_str):
6
+ """Convert hex color string to RGB tuple"""
7
+ hex_str = hex_str.lstrip("#")
8
+ return tuple(int(hex_str[i:i+2], 16) for i in (0, 2, 4))
9
+
10
+ def random_color():
11
+ """Return a random RGB color"""
12
+ return (random.randint(0,255), random.randint(0,255), random.randint(0,255))
13
+
14
+ def lerp_color(c1, c2, t):
15
+ """Linearly interpolate between two colors"""
16
+ return tuple(int(a + (b-a)*t) for a,b in zip(c1,c2))
17
+
18
+ # ----------------- GUI Elements -----------------
19
+ class Button:
20
+ def __init__(self, rect, text, color=(200,200,200), hover_color=(255,255,255), callback=None):
21
+ self.rect = pygame.Rect(rect)
22
+ self.text = text
23
+ self.color = color
24
+ self.hover_color = hover_color
25
+ self.callback = callback
26
+
27
+ def draw(self, win):
28
+ mouse_pos = win.mouse_pos
29
+ cur_color = self.hover_color if self.rect.collidepoint(mouse_pos) else self.color
30
+ win.draw_rect(cur_color, self.rect)
31
+ win.draw_text(self.text, pos=(self.rect.x+5, self.rect.y+5))
32
+
33
+ def check_click(self, win):
34
+ if self.rect.collidepoint(win.mouse_pos) and win.is_mouse_pressed(1):
35
+ if self.callback:
36
+ self.callback()
37
+
38
+ class Label:
39
+ def __init__(self, pos, text, color=(255,255,255), font="Arial", size=16):
40
+ self.pos = pos
41
+ self.text = text
42
+ self.color = color
43
+ self.font = font
44
+ self.size = size
45
+
46
+ def draw(self, win):
47
+ win.draw_text(self.text, font=self.font, size=self.size, color=self.color, pos=self.pos)
48
+
49
+ # ----------------- Optional GUI Manager -----------------
50
+ class GUIManager:
51
+ def __init__(self):
52
+ self.elements = []
53
+
54
+ def add(self, element):
55
+ self.elements.append(element)
56
+
57
+ def draw(self, win):
58
+ for e in self.elements:
59
+ e.draw(win)
60
+
61
+ def update(self, win):
62
+ for e in self.elements:
63
+ if isinstance(e, Button):
64
+ e.check_click(win)
@@ -0,0 +1,62 @@
1
+ from .window import Window
2
+ from .assets import load_image
3
+ from .sprites import Sprite
4
+ from .gui import Button
5
+
6
+ class Toggle:
7
+ def __init__(self, rect, value=False, color=(200,200,200), hover_color=(255,255,255)):
8
+ self.rect = rect
9
+ self.value = value
10
+ self.color = color
11
+ self.hover_color = hover_color
12
+ self.hovered = False
13
+
14
+ def update(self, win):
15
+ mx, my = win.mouse_pos
16
+ x,y,w,h = self.rect
17
+ self.hovered = x <= mx <= x+w and y <= my <= y+h
18
+ if self.hovered and win.is_mouse_pressed(1):
19
+ self.value = not self.value
20
+
21
+ def draw(self, win):
22
+ draw_color = self.hover_color if self.hovered else self.color
23
+ win.draw_rect(draw_color, self.rect)
24
+
25
+ from .window import Window
26
+
27
+ class Slider:
28
+ def __init__(self, rect, min_val=0, max_val=100, value=50, color=(150,150,150), handle_radius=10):
29
+ self.rect = rect # x, y, w, h
30
+ self.min_val = min_val
31
+ self.max_val = max_val
32
+ self.value = value
33
+ self.color = color
34
+ self.handle_radius = handle_radius
35
+ self.dragging = False
36
+
37
+ def update(self, win: Window):
38
+ mx, my = win.mouse_pos
39
+ x, y, w, h = self.rect
40
+
41
+ # Start dragging if clicked on the slider track or handle
42
+ handle_x = x + ((self.value - self.min_val) / (self.max_val - self.min_val)) * w
43
+ handle_y = y + h // 2
44
+ if win.is_mouse_pressed(1):
45
+ if (x <= mx <= x + w and y <= my <= y + h) or ((mx-handle_x)**2 + (my-handle_y)**2 <= self.handle_radius**2):
46
+ self.dragging = True
47
+ else:
48
+ self.dragging = False
49
+
50
+ # Drag the handle
51
+ if self.dragging:
52
+ rel_x = max(0, min(mx - x, w))
53
+ self.value = self.min_val + (rel_x / w) * (self.max_val - self.min_val)
54
+
55
+ def draw(self, win: Window):
56
+ x, y, w, h = self.rect
57
+ # draw slider track
58
+ win.draw_rect(self.color, (x, y + h//2 - 2, w, 4))
59
+ # draw handle as circle
60
+ handle_x = x + ((self.value - self.min_val) / (self.max_val - self.min_val)) * w
61
+ handle_y = y + h // 2
62
+ win.draw_circle((255,0,0), (int(handle_x), int(handle_y)), self.handle_radius)
@@ -0,0 +1,106 @@
1
+ import math, os, re
2
+ from .assets import load_folder_images
3
+ from .animation import Animation
4
+
5
+ class Player:
6
+ def __init__(self, pos=(0, 0), speed=5, size=(32, 32)):
7
+ self.x, self.y = pos
8
+ self.speed = speed
9
+ self.width, self.height = size
10
+ self.animations = {}
11
+ self.current_anim = None
12
+ self.image = None
13
+ self.rect = self._make_rect()
14
+
15
+ # make a lil rect obj for collisions
16
+ def _make_rect(self):
17
+ return type("Rect", (), {"x": self.x, "y": self.y, "w": self.width, "h": self.height,
18
+ "topleft": (self.x, self.y)})()
19
+
20
+ # === animation setup ===
21
+ def idle(self, folder):
22
+ imgs = self._load_sorted(folder)
23
+ anim = Animation(imgs)
24
+ self.animations["idle"] = anim
25
+ self.current_anim = anim
26
+ return anim
27
+
28
+ class walk:
29
+ @staticmethod
30
+ def cycle(folder):
31
+ imgs = Player._load_sorted_static(folder)
32
+ anim = Animation(imgs)
33
+ anim.loop = True
34
+ return anim
35
+
36
+ @staticmethod
37
+ def once(folder):
38
+ imgs = Player._load_sorted_static(folder)
39
+ anim = Animation(imgs)
40
+ anim.loop = False
41
+ return anim
42
+
43
+ class jump:
44
+ @staticmethod
45
+ def cycle(folder):
46
+ imgs = Player._load_sorted_static(folder)
47
+ anim = Animation(imgs)
48
+ anim.loop = True
49
+ return anim
50
+
51
+ @staticmethod
52
+ def once(folder):
53
+ imgs = Player._load_sorted_static(folder)
54
+ anim = Animation(imgs)
55
+ anim.loop = False
56
+ return anim
57
+
58
+ # === main update ===
59
+ def update(self, dt, win):
60
+ moving = False
61
+ if win.is_key_pressed("left"):
62
+ self.x -= self.speed * dt * 60
63
+ moving = True
64
+ if win.is_key_pressed("right"):
65
+ self.x += self.speed * dt * 60
66
+ moving = True
67
+ if win.is_key_pressed("up"):
68
+ self.y -= self.speed * dt * 60
69
+ moving = True
70
+ if win.is_key_pressed("down"):
71
+ self.y += self.speed * dt * 60
72
+ moving = True
73
+
74
+ # update animation
75
+ if moving and "run" in self.animations:
76
+ self.current_anim = self.animations["run"]
77
+ elif not moving and "idle" in self.animations:
78
+ self.current_anim = self.animations["idle"]
79
+
80
+ if self.current_anim:
81
+ self.current_anim.update(dt)
82
+ self.image = self.current_anim.get_frame()
83
+
84
+ self.rect.x, self.rect.y = self.x, self.y
85
+ self.rect.topleft = (self.x, self.y)
86
+
87
+ def draw(self, win):
88
+ if self.image:
89
+ win.screen.blit(self.image, (self.x, self.y))
90
+
91
+ # ---------- helpers ----------
92
+ def _load_sorted(self, folder):
93
+ imgs_dict = load_folder_images(folder)
94
+ return self._sort_images(imgs_dict)
95
+
96
+ @staticmethod
97
+ def _load_sorted_static(folder):
98
+ imgs_dict = load_folder_images(folder)
99
+ return Player._sort_images(imgs_dict)
100
+
101
+ @staticmethod
102
+ def _sort_images(imgs_dict):
103
+ def extract_num(filename):
104
+ match = re.search(r'(\d+)', filename)
105
+ return int(match.group(1)) if match else 0
106
+ return [img for name, img in sorted(imgs_dict.items(), key=lambda x: extract_num(x[0]))]
@@ -0,0 +1,25 @@
1
+ import pygame as py
2
+
3
+ class Sprite:
4
+ def __init__(self, image=None, x=0, y=0, w=50, h=50, color=(255,255,255)):
5
+ self.image = image
6
+ self.x = x
7
+ self.y = y
8
+ self.color = color
9
+
10
+ # optional rect if no image
11
+ if image:
12
+ self.rect = self.image.get_rect(topleft=(x, y))
13
+ else:
14
+ self.rect = py.Rect(x, y, w, h)
15
+
16
+ def draw(self, screen):
17
+ if self.image:
18
+ screen.blit(self.image, (self.x, self.y))
19
+ else:
20
+ py.draw.rect(screen, self.color, self.rect)
21
+
22
+ def move(self, dx, dy):
23
+ self.x += dx
24
+ self.y += dy
25
+ self.rect.topleft = (self.x, self.y)
@@ -0,0 +1,13 @@
1
+ from .sprites import Sprite
2
+
3
+ class TileMap:
4
+ def __init__(self, tile_size):
5
+ self.tile_size = tile_size
6
+ self.tiles = []
7
+
8
+ def add_tile(self, image, x, y):
9
+ self.tiles.append(Sprite(image, x, y))
10
+
11
+ def draw(self, win):
12
+ for t in self.tiles:
13
+ win.draw_sprite(t)
@@ -0,0 +1,203 @@
1
+ import pygame
2
+ from pygame import image
3
+ import os
4
+ import sys
5
+ import contextlib
6
+ import base64, io
7
+
8
+ DEFAULT_LOGO_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAd80lEQVR4nO2dz2tbWZbHv1XSPCEhYcMTMjI2FuNCppuEBLwYkk0XU1C1Sa0Geuhdbbu3/Q8MTM+y1sPMqnrRDBR0byoMdEM1qaFJVh6qSOhgk4CCjIyNNKPMExJPSNQsTr2yYuvHe9I998d75wNCjqO8d2O/+73nnnPuOe8Bx99DuI3fBBofmh6FwMWrPwJv35gehXHeNz0AawkD0yMQuLh8LpP/B0QAFjHqmR6BwMGwC5w/Mz0KaxABWMR0bHoEgmrCADh7bHoUViECsIygY3oEgkpe/0mE/QYiAMuQhyU9tJ/Ktm4OIgDLGMoDkwq6p8DVC9OjsBIRgGWMJRLgPGEgTr8liAAsQ0KBbjMJZd+/AhGAZYgF4Dbnz2TfvwIRgGWMB6ZHIKzL5XOgd2Z6FNaTNz0A6wkDoFAxPYr18Dx65XJAqXT9vWp1vesNh8B0ev0+HgNhSF+PRurGvSmS7BMbEYBVjB0QgFwOKBZpkkfv0YRXSXTNyoKfRxAAnQ6JwXSq/v5xmISS7JMAEQAXKZdpMm5v04peKJgeEVGpAEdH9PVwSIIQBMDbt/rGIE6/RIgArCK4ACq7ZsewtUWTK1rh8w782iIrZGeH/tzvX7+4rIP2U2BwwXPtlOLAk5RBPI9W91rNntV9U7a36QXwiEG/Jck+SSnXRQBWMg313KdcpgkSrfRpJhKDyYRE4Px8MyEYdoHWE2XDSz1+E6gfA4WKCMBKuNKBczmaBNHEd8GsV00+TxGJahXodoFeDxgkDL1OQqD1jez7V+GVadJXj975dgafOoNEk37WHBaISAj6feDyMr4QSLLPcoo+sHsMbDfm/rUIwCpUrCxbWzTh142/Z4lIHPv965DiIiTZZzHlOk38FQ5sEYBVrLu65HKA76fLkaeTSAguL4GLi9s+gqAjyT7zKPpA7c4tU38RIgCq8TygXs/uvl41Ozv0s2y1rrcF0SEf4RqvDOw/XGjqL0KeUFVEE1/MfPUUCpRg1O1SxOCvj8XpF5HzgL0HsVf8m4gAbEpk6tfrsuJzU60ClRIwvAe8+LPp0Zgl5wG1u7TP3wB5YuOw6EBQsQgcHsoeXyeFEvDwH4HdI+DJF8DYokNIuqjdocmv4IzKe9IYJAbNR7e9qbUasL9vZjwCEQ6Bx58DvXPTI9FDuU7NahQeTpN6AEnJ5WjVl8lvnkIJePRr4OCe6ZHwUvRpETr6VPnJVBGAJORyQLMpSTw2USgBn/wKaD4wPRL1RA6+n/4D24E08QHEJZr8ac/Td5UPP6P3s5TkBtTuUOpunte/JAIQl8NDmfy28+FnQK/ttk+AYZ+/DNkCxGFvf3EVHMEuHv0aKPumR5Ecxn3+MkQAVuEVgf2m6VEIcSmUgIc/Nz2K+OQ8MvXnRZo0IFuAVXz8S3qoBHdo3KfIwJvvTI9kOX6TnHzM+/xliAWwjOYDSjgR3OPDz8h6sxGvDBx+THt9g5MfEAtgOcePTI9ALTfLegPXpb0Bymj0vNv/Lio8Olte3HYKJeD4U+DZl6ZHco2i9F2ViAAs4vgRUHHwYM9kQmfow5BOz0XvcYj7uVzuujJxqURf23gOovkAOPnKjnThrQM6rWdZiXkLf2uW4EpiyXBIr8GA3nU06JhOqdT3bLnvYpEiJVFdQxsolIAHPwe++a25MXhlMvVNV5ZegAjAPJoP7F39o0KaOid8HEYjel1d0ZbB9+ll+qBU4z5tA3RbAZF3328a3+cvQwRgHrbt/aMGG/2+PRN+GeMxVfG5uKByaDs75qyCQokEXefx4a0DKxx8cRABuMnBPTtW/yCgKrlBQBPKVaKtQrl8Xd1HN3c/0iMAa1blMYkIwE3ufmTu3kFANfBGI7cn/TwGA3qVy9ftw3RRqQL1JnDBVEDUEXN/HiIAs/h7ZuL+69bEd5HBAHj6n0DjUO/PuvmARwCKPtD4GVCywGpMQtABOiciAO/QfKj3fv0+0G6nb7VfRtCh7r0vQL6W40/13LdxX200IOfRPt8hcx/AjxM/6qEoAjCLrtDfcEgTPwsr/ixh8G4135PHQNCjUB13unXkDFRxXNiCFN7EDLvUO/FGHwURgIiDe3py/vt94PVr/vvYRlTK+2Y137NnVNrrk1/xj6FxfzMBsDymP5cwAC5OFjZQEQGIaNznv8dwSPXts8iyFl5vvqOMPe7tQH2DU52aCnQoYxLSz3xF56Q8aneotfI4Y+boLF4RONKw/2+31bXDdon209UtvJ5/zZ+AVSglPyVYrgP7D9xx8k1CMvWvnsfqnZDH/kOKXQYd6rX29o2GUVqGjtU/yt7LGt1TeiBXMR6RTyAq7cVF4348Adiw4YYRuqe06idomnK9Bajs0isMyCK4ep4dq0CHAFxe8t/DNoIO8Oab+J9vfUv+AE5fTJxogEOZfABuefaTcNsHUKgAO3fpFXTIdEtzB1avyC8ASU7kpYWbHv84jEckApzbsWXbANcy+SYh0HqykdW+3AkYWQV7D0gE0mgV6Fj9r67472ETizz+cTh5zO+P2T16VwCiTL6du7z3Vcnlc/Lub9gjMV4UIF9Ir1Wga/+fFSYhJfqs21Z90AO6baDK2Hilce+6UIil5/QXMuwCrW/W//neIHkYcNYq6LdIhVy1CnSY/64f5knKsnBfXC7OeAWgUgW26kD1rlvm/sVJPIdqAtbPA8gXyENaPXLXKtgkLhyXLK3+l8/VPANnT/kPZf3dZ3T+wgWCDu31GRZaNYlAs1bB1Qugd+qGVSDmvzqiEJQKeuf824By2X4BCAPKoWAMzavNBMwXqODh7vF1/FfRXoUFbgEYDrNh/g+7ycJ9cWDfBli+51fk5FsFX1nw6hE1NWw+omwq2/D3+HP/g4D3+jYw7JLTTzVnT9Vfc5ZFFZBNMwmBV39MnNCzLvxnASq7wNHuykMJ2tFh/qddACKPP8eD2jsHgi5vanClYtc2oHuq3pJagb7GIIUKZVfd+QXFXHOG1VeHAzDNyT+ckz+ixdzZp1zmvX5chl3g9Cvtkx8w0RmoUCEfgWkh4K5GM9t8I428/hO/f6dzynt9001OJiGl8L78w1ppvCowdxz4psNQZz6BjtU/zc6/1hM9Dyx3bz+TAsAY2kuCHfUAonyCfovCHtw/FH+P9/oAWQBppKPZj9M55bXWymW9WzUNob0k2CEAEdsNevVbFAbhWmV8xvBSRBoFILLUdHJxxisApZI+AVjjuC43dglARCQEGxxzXErFV3u9eaRt/2/AQ033bfNeX0coMOgAbQUp0gzYKQARsyFElftOHRZAmgSAI9EnLr1z3utz+gFiluUyid0CEFGoAEefqts/6Sj+6UILrzhwJfrEwSsD/h3qh8jVfZjLAtDlz9oQNwQgolABPvhks8NHOiIAaSGa/Lr3rDfLcQ0GfC3FVDcvVW2tMuOWAEREh4/qx8ktAq/IN66IMOS/BzeTkM6d65z8Xhmo3b3dYms45O0pqCISEBXj1O0k3RA3BSAisgiSpBlzHjCJcD0FeNOiHknJeTTxd4/n/73tERVHzP15uC0AEVGacf14tRDocAC6fARY5+RftOLfhNuhum4o0DFzfx7pEICIOEJQ1hACdPkMgI4U36JPjTbiltzm/nnmcsn/TefEOXN/HukSgIhlQsC9BQgCd0OAr/7Iu5qV61RX0pUyXPOwJIVXFekUgIhICGp3SLGnGn5pLpv/H3xC78MuOf+GvR/eu2QVrPvQF33qrrNJT70g4CviUakAFyuELwXm/jzSLQARpSo93Fsajn+67gAErttgLZqwk5AEIRKJiHFAE2X2z/Vjt7rrzCMl5v48siEAERXGUBJA4b+0JAAtI1+4FgeXzflVOOzdj0u2BID7+KfL5r/t6PSrpNTcn0e2BKDInAQkAsAHdzIQwFZ732ayIwCex5dPDlC+usvhv6yjuyiNJWRHAGT1d5t1YvVxSdrFOEXorwloCu468LL682K6fl9KyY4AcD9AaQj/ZRXumgMWkx0B4LQAwjDdRUDTztjyw0aMZEMAuMs+ZSH2bxrbW3k5igiACmw/ruo63L8/7rqDFpMNAVBd9eUmsv/nhVsAxtm14LIhANwPkKun/1yB2/zviQUgbIL4AHjhzgAUC0AQLCWX4w3hZnj/D4gACLbDvfoP7GvWoZPspAILbuIzl3ALp1SzIKNkQwC4nXS6G0xmBc/TEP8vL65GnAGysQXgjtNzHlTJMvU6/z0yLtzZEAAdZaUFtXgeUK3y3kPyNzIiANxhOm5HVRbZlwYuOsiGAAC8v+xSSbYBKimX9Yiq1HDIkABw+wG4vdVZIZcDDg/575OVAq4ryI4AcJt7tRrv9bPC4SFv6bYIWf0BZEkA3r7lvX6hQKarsD57e/qO/faynQAUkR0BAPhVf3eDzjdZx/eBnR099woCMf9/IFsCwL0NqFSArS3ee6QR3wcaDX33k9X/R7IlADr2fY2GRASSoHvyTyYiADNkSwDGY34RyOf1PtAuo3vyA8DVld77WU62BADQYwVsb0tYcBUHB/on/2QiAnCD7AlAr0cPAjd7e/zNSFwklwN+8hP+NN95XF1J9aYbZE8AAD17wHweaDZFBGYpl2nymzg7Iav/XEQAOBERuGZvDzg64i/Quojzc1n955BNARiN9B0EiUQgq+FBz6P/v64Y/zyCQDz/C8imAAB6H4h8HvjgAz3n223C94G7d8039Whnu+7fMrItAGGo9567u+T9TnueQLTq2xAObbcl628J2RUAALi40H/PajXdfoF63Y5VH6CQrzj+lpJtATBhBQDkBf/pT8kxlhZroFgkYbPlPEQYAq2W6VFYT7YFADBjBUTs7FBYzHUHYa1GgmbDqh/RaonXPwbZqAq8jF6PHmBTdf0KBXIQBoF7+9WtLVrxbauJ2G5nvthnXMQCAOzwElcqtIoeHvL3MtyUrS0y9z/4wL7J3+nIvj8BYgEAtFp0u2bSU2+yvU2vIAAuL/kLmcTF82hctZq5ZJ5VdLtmt3QOIgIQcX5OD7iOclRxqFToFYa0ovX7dJpRJ1FjDt+3a38/j24XePPG9Cic4z385F++x3SNB8urAAXLH4qkbG2RWWsrQUBCMByq3+PmcuTJLxTovVKxz7xfxHAIvHxpehRO8h5w/P1GV8h5QNEHSj6JwnbDbWE4PHSnzn+Uzjydrlf1uFLh777Ljaz8G7G5AMwj55EQFH33BCGXIweXy5MiK8jk3xgeAbjJ1gGw/9AdIYiSWmzxBwi3abXkgI8C9AhAhFcGaneBnbvabrk2tvsDsoxMfmXoFYCInAf4TbIKbMZEzTphMZMJ8Pq1JPkoxEwi0HQMXL0wcutE9HqST24LwyFwdiaTXzGyyV1Fr0cPn/gEzNHtSkUfJsymAgcdo7ePzWhEq4+Jk4NZZjIBXr0iT79MfhbkLEBcRiPgxQtKzxX4CUMSXVtSoVOK2LRJOT8nMdjbky0BF2Lya0Oe4HXo9SgLr9GwP0feJSYTcrrKqq8NswIQBoCr82c8JhPV98UaUEG/L0U8DGD2qR2nIKQTlRU7OjI9EjeRVd8osmxtiufRASIhOVFsX1Z9Y5iNAgy7Rm+/MbkcTX4x/9ejVEpPUVRHMSsA69QhsIm9PTk1uClZa5ZiGWYFYOBw+aaDAztKiLlOtUpNQwUjmE8ECjX16FNJrSaTXyUm+wZmHPMCMHZMAIpFYH/f9CjSxfa2WAGGMC8AgUPbgKhQiKAesQKMYF4AXIkE5HKU+Scefx62t9PbL9FizAuAK8lAh4fi8eemVjM9gsxhXgBGDpR2OjjIXs7/ZHJdhrzTua5AzEm1an9XpJRhhz0bdICKJV1lb2LS4x/Vu/c8et0s4b1uSe+bZcTH4+taB+Px/AYkw6EeEazXpdKvRiwRgAs7BaBcNufxj3LkgXcnpamceV1NS6tVOQqsEfNbAMBOR6DJHP/JhHLkbeoUPB6v13xkHcQXoA07BMC2jEDTOf5R0RHb0FWKWwRAG3YIwHRsV0agSY//q1f21rzv9cg64SafpzoLAjt2CABgTzSgVjPj8XfhXPx0Sp2KdSBWgBbsEQAbKgRvbZlz+rnS7ebqSo8VUCpJerAG7BGAoeGHv1g01wXI9pV/lumUfBQ6kPRgduwRAJOOQJNOP1dW/lmiZincbG9LYhAz9ggAYC4c2GgAhYL++7bb7k3+CF0t08QZyIplAmBgMuzt0Uqjm05Hn0ONg9GI0oS5EQFgxS4B0B0J2Noys8/sdoELy3If1qHd5r9HoUC/J4EFuwRApwVgyuk3HKYn1308JjHjRqovsWGXAOhyBJo62x+VwU4TOiyZ7W2pHsyEXQIA6HEEmqjmGyX6pO2Qiy4rwISfJgNYKADM2wDf129S2ni4RyU6rADJDGTBPgHgdAQWi7T668bWwz2qGI/5IwKlkuQEMGCfAHAdCjKV7HN56W6sPwmXl/z3kG2AcuwTAK7CIHt7+pN9hkN9abOmGQyuqwpxITkByrFPAHyGstsmynqFIfD6td57mobbFyDbAOXYJQBFH8grXqVNNfJ4/Xp+bb000+/znxTMWnFWZuwSgO2G2utF8X7dtNvpdvotYjqlrQAn4gdQimUCcKD2evW6/nh/t+t2jv+mcOcEiAAoxR4B8MpASeE+vVjUn+cfhtlx+i3i7Vv+bYAUClGGPQKg2vtvwvR//Tp9mX7rwJ0TIH4AZdgjACr3/76v3/TP6r5/HtwCINsAZdjRGCTnqRWAel3dteISNbccDGgCZNkS4C5vJj0alWGHAKic/OWymeo+lQq9onyDICCfQFYFod/nXanLZf6IQwZInwDYki12UxAmE3pgh0N6uVIEdF2CgFcAKhURAAXYIQBlhSa7rQ6ifJ4mxOykmEzIbzAckoUQBIubc9pOLkdboKhhKXfGnmwDlGBeAMp1ddl/uZwZ839d8vlrS+Emw+G7/fgikZhOzTobcwBqPwh2NG4ToispwUowLwAqzf9iUd21TFMq0WuZGR2JwqrvJWFRy3HbLCuxAJSQLgHIGi5MVE7EEbgxZvMAij5QyNADK6hF6gRujFkBUH30VxJxsoVsAzbGrABUFCfsTKf8RSkEexALYGPMCYDqwz8ROrrVCHYgFsDGmBMALudflo/iCkJCzAkAR+kvQF+desE8nifbgA0xIwBc5n/E+Tn/mXTBPIWCmYNfKcKMANTu8l5/OnUznVZIzs5OuhLANGNGALiTf0y0/hLMsctUSj4D6BeArQPe5B9TLb8Fc2xvy9mANdEvAJyrv6kqwIJ5pErQWqRLAEy0/hLswJY6EI6hVwC2DtQ3/vjx2lvZOggjvEupJM7ANdArANUjvmub6P4j2IVYAYnRJwCqC3/OUq+7VQhE4EH8AInRJwBcmX+5HDX/FIRCQbYBCdEnAFzJP7Wafsdft00vwT7ED5QIPTOHq/CHidX/9CnwzW+v/1xvAv4eUKnS+y6jn0NYTaUiB8ISoEcAaneYrqt59X/+NfDsy3e/d3FGr1n8PeDOR8DRQ31jEwjpG5gI/tnjlXm8/7pX/2739uRfRO8cGPR4x+MCQRcIevTzGA+BzhngFYFPfsV3z3yesgLlLEgs+AXAZzKJfV/f6t/tAq/PVn9uliBjAnDyFRAOabIHveUCePqU1zoqFkUAYsI7g3Ier/mvg34fePMGGCWc0JwCEHSBs2e3v1/2gcqSWHjFJ18FB505W6FFtL7lFYBSKf2dlxTBKwDbDZ7MP9/XE/cPQ6DV4r9PUoIecPI4+b/7+Jd8ApCEN9/xXl9OgsaGNwxYP+a5rq6Mr1YrPU09yz7QuM93fX8v2ec7pzzjAKRKUAL4BMBv8oT+ikU9sd7Ly3ebTgQX/PfkpHGP9/qFhKtu75xnHIDkAiSATwC4Vn8de//JBLhwfMLfpMkckgyHyT4/Tvh5gQUeAajd4Uv80ZHvfXWVHtMfIPO/ynxYKumKzp1JKfkAsVAvADmPb/Xf3uYP/S1a/acONxzZZTqHMUvSFX0sXZxsQL0A1I/5zvzrMP8XpZEOHY7rczr/IpJaAHFDhgIragWg6AM7TId+PI8/vDOZpDOPvM5sAbS+5b3+OogjMBZqBWD/gdLLvYOO0F+/n669P0DhuaQe+qSsKwCcoUAhFuoEwG8CFcbyzDoEIG2ef0CP+W+jBSDEQo0AcDr+AIr9c2f+9fvpzB/XYf6v69ATR6Bx1AjA3gPeWv869nOr+gkOHLUOuOsTbLL696Soimk2F4BynbfYJ8Bv/odhOg+PJE3PXQcx/51mcwHgdPxFcHv/+33e65uC2/w/fSpmvONsJgD1Y94uv4CeIo89h2P8y/CZs/8klu886wtA0Qd2GR1/Edwnu8IQGKV0FVtWG0AFYv47z/oC0PiZwmEYxEXzP26xEU4HYOdUzP8UsJ4A6DD9deGi+R+n3mCZefUX8z8VJBcAXaa/DsT8Xx/b+yKkMaeDgWQCkPOAw4+ZhrIAzl9kEPBdmxMvRlSEWwBstwBCh09vaiSZANSPeRN+5jEe8/0yXU39jRPf5xSAcGj//l8sgFjEF4Byne+k3yo4TuilNfVXB6oy+LgqJ08m8ruNSTwBMGH6z9LrqbUCJhOgbfkedlM4cwBUTVyuMOJsLUdhKfEE4PBjviIfcZhO1ZbnPj9P/wrhMSZQqep6NB7xiICLoV1DrBaA2h3eY75xGQzUiEC77WboL608/1rt9cIh0LtUe80Us1wAij6wb1GDy15vMxHodNb3J+S89e8rLObiTG1hkBdfA62/qLteylksAKb3/Yvo9YC//jWZTyAMgVevNvP6FzU1I8kiT36bvKz4PFrfUsek3hnQb21+vQywWAAaH+oP+cVlNAJeviRzfpkQDIf0mZcv9R739YrA3Y/03c91Bj3g8eebiUC3DTz54vrPrSfARHIBVjG/xrbfpL5+NjOdkjl/dUUFQ70bJroJT3C9SRNfRxmutNE7B37/G+pfmLSHwclXt3slTsfA+TNayISF3BaAok8VflxiPDbr1feK9OByV9+xBa5zBoMe8IffAHf+Hjj+dHUx05OvgNNni6MSvTNayGxfzAzyrgDkPDrlZzLkZyuLnID+nj1dd3Vx9JAm55MveDICX/yZXs0HZE3NhjTHI3Iatr6LF45sPQHu/EKe6QW8KwCND9Nzyk81pSrw9s273/P3gEe/5i+7bSON+/R/f/w5X1rw2TN6bcJ0DFyc2BXNsohrJ6AL+37b+Nln9k5+zu67EdV9EgHOpCMVXL0AhiuKvmYUEgAX9/2mOX7E33BzE3R1363u037ddlrfmB6Blbwv+/6YVOrXX3tFNx56Xdz9iL8AyaaMekDnxPQorON92fevQVOspVu4kPdw9RwIHa0BwcT7su+PiTeTFCVx/ts07pkewWqmY6D91PQorEJ9e/C0MpsVmZV4fxIqVfu3AQBFcoKO6VFYgwhAEuRA0HK4y5CpQnwBPyICkARbDgTFmWi2F+00yeBCDgv9gAhAWrG9Zp9pxBcAQAQgGVFhFNOra5ySXCY673LV+ONgPAC6CusQOIoIwDqYbmsdZ3WPcuZ1EQ7VlQrTxYX4AkQAkhAlA5muiR93Yqsut7UMF/sEjgeZ9wWIACQhigKcPVNTwWZdWt/F+9yb7/RNzJvn8V3h8rnpERhFBCAJsxmTf/pXM2NofZvM1H7yBb/P4vnX7pn/EYOLTOcFiAAkxSvT+8WZXhM74umXyT4/HtGRXS4R6LapMIfL9Cxvc8aICEBSZlOCn32pNyJw8tV6K+14RJV2VE/UoMtbD0AXvbPM1g8UAUjKzR4Jjz/X420/fbr5PvvkMfD7f1YjWt02bYNcn/wRVy9Mj8AIOex++k+mB+EU0zHwv69n/jy5dgrW/hbI/43a+4VD4C+/A/5bkZNt9H/Ay/+iryv+egVNnn8NfPMFMPgfNWOygXFgrvelQd7D8b99b3oQThEGwIv/WPz39SYVC9n0wFC3DZw9JXHhXGXrTTrd6O9RP8F5ghB0Kcmn9W38Wnwucvhx5qpiiQCsw8m/r/6MV6SJVfHplNyq/P0ocad3TpMtrZPMZvxm5sqIz+8LICynXKfw0TLGo80LWgp66bfIGZih6ljiBFwHG5qlCuqZjjOXGSgCsA4lS44FC+oRARBWUq6v/ozgJjd7P6QcEYB1yBfsKQ4iqCdDVoAIwLpUxApILSIAwkrEEZheMnQ4SARgXcQPkF7Gg8y0EhMBWJd8Adg6MD0KgYtgRZ5HSvh/St3ukH8llrUAAAAASUVORK5CYII="
9
+
10
+ class Window:
11
+ # friendly string -> pygame constant mapping
12
+ KEY_MAP = {
13
+ "left": pygame.K_LEFT,
14
+ "right": pygame.K_RIGHT,
15
+ "up": pygame.K_UP,
16
+ "down": pygame.K_DOWN,
17
+ "space": pygame.K_SPACE,
18
+ "enter": pygame.K_RETURN,
19
+ "backspace": pygame.K_BACKSPACE,
20
+ "tab": pygame.K_TAB,
21
+ "escape": pygame.K_ESCAPE,
22
+ "Rshift": pygame.K_LSHIFT,
23
+ "Lctrl": pygame.K_LCTRL,
24
+ "Rshift": pygame.K_RSHIFT,
25
+ "Rctrl": pygame.K_RCTRL,
26
+ "alt": pygame.K_LALT,
27
+ "keya": pygame.K_a,
28
+ "keyb": pygame.K_b,
29
+ "keyc": pygame.K_c,
30
+ "keyd": pygame.K_d,
31
+ "keye": pygame.K_e,
32
+ "keyf": pygame.K_f,
33
+ "keyg": pygame.K_g,
34
+ "keyh": pygame.K_h,
35
+ "keyi": pygame.K_i,
36
+ "keyj": pygame.K_j,
37
+ "keyk": pygame.K_k,
38
+ "keyl": pygame.K_l,
39
+ "keym": pygame.K_m,
40
+ "keyn": pygame.K_n,
41
+ "keyo": pygame.K_o,
42
+ "keyp": pygame.K_p,
43
+ "keyq": pygame.K_q,
44
+ "keyr": pygame.K_r,
45
+ "keys": pygame.K_s,
46
+ "keyt": pygame.K_t,
47
+ "keyu": pygame.K_u,
48
+ "keyv": pygame.K_v,
49
+ "keyw": pygame.K_w,
50
+ "keyx": pygame.K_x,
51
+ "keyy": pygame.K_y,
52
+ "keyz": pygame.K_z,
53
+ "0": pygame.K_0,
54
+ "1": pygame.K_1,
55
+ "2": pygame.K_2,
56
+ "3": pygame.K_3,
57
+ "4": pygame.K_4,
58
+ "5": pygame.K_5,
59
+ "6": pygame.K_6,
60
+ "7": pygame.K_7,
61
+ "8": pygame.K_8,
62
+ "9": pygame.K_9,
63
+ "f1": pygame.K_F1,
64
+ "f2": pygame.K_F2,
65
+ "f3": pygame.K_F3,
66
+ "f4": pygame.K_F4,
67
+ "f5": pygame.K_F5,
68
+ "f6": pygame.K_F6,
69
+ "f7": pygame.K_F7,
70
+ "f8": pygame.K_F8,
71
+ "f9": pygame.K_F9,
72
+ "f10": pygame.K_F10,
73
+ "f11": pygame.K_F11,
74
+ "f12": pygame.K_F12,
75
+ "insert": pygame.K_INSERT,
76
+ "delete": pygame.K_DELETE,
77
+ "home": pygame.K_HOME,
78
+ "end": pygame.K_END,
79
+ "pageup": pygame.K_PAGEUP,
80
+ "pagedown": pygame.K_PAGEDOWN,
81
+ "capslock": pygame.K_CAPSLOCK,
82
+ "numlock": pygame.K_NUMLOCK,
83
+ "scrolllock": pygame.K_SCROLLOCK,
84
+ }
85
+
86
+
87
+ def __init__(self, width=640, height=480, title="Game Window", icon=None):
88
+ # temporarily silence pygame print
89
+ with open(os.devnull, "w") as f, contextlib.redirect_stdout(f):
90
+ pygame.init() # <- no more hello message
91
+
92
+ self.width = width
93
+ self.height = height
94
+ self.title = title
95
+ self.screen = pygame.display.set_mode((width, height))
96
+ pygame.display.set_caption(title)
97
+
98
+ # --- ICON HANDLING ---
99
+ if icon:
100
+ # user-provided icon logic
101
+ if isinstance(icon, str):
102
+ icon_path = icon
103
+ if not os.path.isabs(icon_path):
104
+ icon_path = os.path.join(os.getcwd(), icon)
105
+ if os.path.exists(icon_path):
106
+ img = pygame.image.load(icon_path).convert_alpha()
107
+ pygame.display.set_icon(img)
108
+ else:
109
+ print(f"⚠️ Icon file not found: {icon_path}")
110
+ else:
111
+ pygame.display.set_icon(icon)
112
+ else:
113
+ default_icon_path = os.path.join(os.path.dirname(__file__), "icons", "default_icon.png")
114
+ if os.path.exists(default_icon_path):
115
+ img = pygame.image.load(default_icon_path).convert_alpha()
116
+ pygame.display.set_icon(img)
117
+ else:
118
+ icon_bytes = base64.b64decode(DEFAULT_LOGO_BASE64)
119
+ icon_file = io.BytesIO(icon_bytes)
120
+ img = image.load(icon_file).convert_alpha()
121
+ pygame.display.set_icon(img)
122
+
123
+ self.clock = pygame.time.Clock()
124
+ self.running = True
125
+ self.keys = {}
126
+ self.mouse_pos = (0,0)
127
+ self.mouse_pressed = (False, False, False)
128
+ self.draw_calls = []
129
+
130
+ # === input helpers ===
131
+ def is_key_pressed(self, key):
132
+ # key can be pygame constant OR string from KEY_MAP
133
+ if isinstance(key, str):
134
+ key = self.KEY_MAP.get(key, None)
135
+ if key is None:
136
+ return False
137
+ return self.keys.get(key, False)
138
+
139
+ def is_mouse_pressed(self, button=1):
140
+ return self.mouse_pressed[button-1]
141
+
142
+ # === font system ===
143
+ def get_font(self, name="Arial", size=16, bold=False, italic=False):
144
+ if not hasattr(self, "font_cache"):
145
+ self.font_cache = {}
146
+ key = (name, size, bold, italic)
147
+ if key not in self.font_cache:
148
+ try:
149
+ font = pygame.font.SysFont(name, size, bold, italic)
150
+ except:
151
+ font = pygame.font.Font(None, size)
152
+ self.font_cache[key] = font
153
+ return self.font_cache[key]
154
+
155
+ def draw_text(self, text, font="Arial", size=16, color=(255,255,255), pos=(0,0), bold=False, italic=False):
156
+ f = self.get_font(font, size, bold, italic)
157
+ surf = f.render(text, True, color)
158
+ self.screen.blit(surf, pos)
159
+
160
+ # === drawing helpers ===a
161
+ def draw_rect(self, color, rect):
162
+ self.draw_calls.append(("rect", color, rect))
163
+
164
+ def draw_circle(self, color, pos, radius):
165
+ self.draw_calls.append(("circle", color, pos, radius))
166
+
167
+ def draw_sprite(self, sprite):
168
+ self.draw_calls.append(("sprite", sprite))
169
+
170
+ # === main loop ===
171
+ def run(self, update_func=None):
172
+ while self.running:
173
+ for event in pygame.event.get():
174
+ if event.type == pygame.QUIT:
175
+ self.running = False
176
+ elif event.type == pygame.KEYDOWN:
177
+ self.keys[event.key] = True
178
+ elif event.type == pygame.KEYUP:
179
+ self.keys[event.key] = False
180
+ elif event.type == pygame.MOUSEBUTTONDOWN:
181
+ self.mouse_pressed = pygame.mouse.get_pressed()
182
+ elif event.type == pygame.MOUSEBUTTONUP:
183
+ self.mouse_pressed = pygame.mouse.get_pressed()
184
+ elif event.type == pygame.MOUSEMOTION:
185
+ self.mouse_pos = event.pos
186
+
187
+ if update_func:
188
+ update_func(self)
189
+
190
+ self.screen.fill((20,20,50))
191
+ for call in self.draw_calls:
192
+ if call[0] == "rect":
193
+ pygame.draw.rect(self.screen, call[1], call[2])
194
+ elif call[0] == "circle":
195
+ pygame.draw.circle(self.screen, call[1], call[2], call[3])
196
+ elif call[0] == "sprite":
197
+ call[1].draw(self.screen)
198
+ self.draw_calls.clear()
199
+
200
+ pygame.display.flip()
201
+ self.clock.tick(60)
202
+
203
+ pygame.quit()
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.4
2
+ Name: crystalwindow
3
+ Version: 1.4.7.5
4
+ Summary: pygame but remade, easy window/player/tile handling
5
+ Author: CrystalBallyHereXD
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Operating System :: OS Independent
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: pygame>=2.3.0
11
+ Dynamic: author
12
+ Dynamic: classifier
13
+ Dynamic: description
14
+ Dynamic: description-content-type
15
+ Dynamic: requires-dist
16
+ Dynamic: requires-python
17
+ Dynamic: summary
18
+
19
+ pygame but easier!. Made by CrystalBallyHereXD!
@@ -0,0 +1,20 @@
1
+ crystalwindow/FileHelper.py,sha256=o1n0_a2Fv1yhaHd0o1imJlFs_9hz4X8GAZRvxpZoBa4,2613
2
+ crystalwindow/__init__.py,sha256=BkXswsLJo-TupGDQtaGwBvsLWf1yqkvYZhqXRoxWAyY,646
3
+ crystalwindow/animation.py,sha256=zHjrdBXQeyNaLAuaGPldJueX05OZ5j31YR8NizmR0uQ,427
4
+ crystalwindow/assets.py,sha256=LR2ocZ0BUxz8qZqEtx1s-WvxwY60b2OmxlzGj_Hyrao,1022
5
+ crystalwindow/collision.py,sha256=hpkHTp_KparghVK-itp_rxuYdd2GbQMxICHlUBv0rSw,472
6
+ crystalwindow/draw_helpers.py,sha256=HqjI5fTbdnA55g4LKYEuMUdIjrWaBm2U8RmeUXjcQGw,821
7
+ crystalwindow/draw_rects.py,sha256=TT9CA9aSkLXq6VtApkuHEGtRHjazXj_ph2cXCwEYJQQ,1945
8
+ crystalwindow/draw_text_helper.py,sha256=CGF2Kk39IxggzY5w-iDA4XzRwxiMDLH0LMBKIQQOO4s,1276
9
+ crystalwindow/fun_helpers.py,sha256=fo5yTwGENltdtVIVwgITtUkvzpsImujOcqTxe8XPzR8,760
10
+ crystalwindow/gravity.py,sha256=tMctHYubpcUJRogz4kNg3XZGDPE3605zON-4YumiVGo,2752
11
+ crystalwindow/gui.py,sha256=ApsavaunPEKufJFIXO1vEDsJpLqUSkMoqSajYAH1swU,2101
12
+ crystalwindow/gui_ext.py,sha256=_91W9amoum0fJ8iHTdOoD8L5bS7n-4XBs-9J5HUb5dA,2274
13
+ crystalwindow/player.py,sha256=4wpIdUZLTlRXV8fDRQ11yVJRbx_cv8Ekpn2y7pQGgAQ,3442
14
+ crystalwindow/sprites.py,sha256=xIVeMIDMKotaQUX4h3jXp1nyt6P5BmelRWrjFz7wAA8,696
15
+ crystalwindow/tilemap.py,sha256=PHoKL1eWuNeHIf0w-Jh5MGdQGEgklVsxqqJOS-GNMKI,322
16
+ crystalwindow/window.py,sha256=ecz-X_pawW11ry7TIkao8Z1txxphLPfEJpZvDel_eyQ,17370
17
+ crystalwindow-1.4.7.5.dist-info/METADATA,sha256=RSAvZEJQZutdB1gndsaFJUgvCNLNjTs-_vzWIzZMJb4,558
18
+ crystalwindow-1.4.7.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ crystalwindow-1.4.7.5.dist-info/top_level.txt,sha256=PeQSld4b19XWT-zvbYkvE2Xg8sakIMbDzSzSdOSRN8o,14
20
+ crystalwindow-1.4.7.5.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ crystalwindow