import pygame import math import random from pathlib import Path pygame.init() ASSETS = Path("assets") # ----------------------------- # COLORS # ----------------------------- BG_TOP = (6, 10, 18) BG_BOTTOM = (10, 18, 30) PANEL = (18, 28, 44) PANEL2 = (28, 40, 62) CYAN = (0, 220, 255) BLUE = (70, 130, 255) GREEN = (90, 230, 150) RED = (255, 95, 110) YELLOW = (255, 210, 90) WHITE = (240, 245, 255) MUTED = (150, 170, 190) SHADOW = (0, 0, 0) # ----------------------------- # SOUND # ----------------------------- def load_sound(name): p = ASSETS / name if p.exists(): try: return pygame.mixer.Sound(str(p)) except: return None return None snd_move = load_sound("move.wav") snd_weld = load_sound("weld.wav") snd_success = load_sound("success.wav") snd_fail = load_sound("fail.wav") # ----------------------------- # FONT HELPERS # ----------------------------- def get_font(size, bold=False): return pygame.font.SysFont("arial", size, bold=bold) # ----------------------------- # HELPERS # ----------------------------- def clamp(v, a, b): return max(a, min(b, v)) def draw_text(surface, text, x, y, color=WHITE, size=24, bold=False, center=False): font = get_font(size, bold) img = font.render(text, True, color) rect = img.get_rect() if center: rect.center = (x, y) else: rect.topleft = (x, y) surface.blit(img, rect) return rect def draw_glow_circle(surface, pos, radius, color, layers=6): x, y = pos for i in range(layers, 0, -1): alpha = int(18 * (i / layers)) s = pygame.Surface((radius * 4, radius * 4), pygame.SRCALPHA) pygame.draw.circle(s, (*color, alpha), (radius * 2, radius * 2), radius + i * 4) surface.blit(s, (x - radius * 2, y - radius * 2), special_flags=pygame.BLEND_RGBA_ADD) def draw_panel(surface, rect, border=CYAN, radius=18, fill=PANEL): pygame.draw.rect(surface, fill, rect, border_radius=radius) pygame.draw.rect(surface, border, rect, 2, border_radius=radius) def gradient_background(surface, top_color, bottom_color): w, h = surface.get_size() for y in range(h): t = y / h c = ( int(top_color[0] * (1 - t) + bottom_color[0] * t), int(top_color[1] * (1 - t) + bottom_color[1] * t), int(top_color[2] * (1 - t) + bottom_color[2] * t), ) pygame.draw.line(surface, c, (0, y), (w, y)) # ----------------------------- # PARTICLES # ----------------------------- class Particle: def __init__(self, x, y, vx, vy, color, life): self.x = x self.y = y self.vx = vx self.vy = vy self.color = color self.life = life self.max_life = life def update(self): self.x += self.vx self.y += self.vy self.vx *= 0.97 self.vy *= 0.97 self.life -= 1 def draw(self, surface): if self.life <= 0: return alpha = int(255 * (self.life / self.max_life)) s = pygame.Surface((8, 8), pygame.SRCALPHA) pygame.draw.circle(s, (*self.color, alpha), (4, 4), 3) surface.blit(s, (int(self.x) - 4, int(self.y) - 4), special_flags=pygame.BLEND_RGBA_ADD) class Spark: def __init__(self, x, y): ang = random.uniform(0, math.pi * 2) spd = random.uniform(2, 8) self.x = x self.y = y self.vx = math.cos(ang) * spd self.vy = math.sin(ang) * spd self.life = random.randint(12, 24) def update(self): self.x += self.vx self.y += self.vy self.vx *= 0.94 self.vy = self.vy * 0.94 + 0.08 self.life -= 1 def draw(self, surface): if self.life <= 0: return alpha = int(255 * (self.life / 24)) s = pygame.Surface((6, 6), pygame.SRCALPHA) pygame.draw.circle(s, (255, 220, 90, alpha), (3, 3), 2) surface.blit(s, (int(self.x) - 3, int(self.y) - 3), special_flags=pygame.BLEND_RGBA_ADD) # ----------------------------- # MAIN BLOCK # ----------------------------- class FiberWeldBlock: def __init__(self, screen, rect): self.screen = screen self.rect = pygame.Rect(rect) self.reset() self.noise = self._make_noise() self.flash = 0 self.flash_alpha = 0 self.title_color = CYAN self.finished = False def reset(self): self.target = random.randint(-40, 40) self.offset = random.randint(-100, 100) self.power = 100 self.welding = False self.result = None self.score = 0 self.particles = [] self.sparks = [] self.flash = 0 self.flash_alpha = 0 def _make_noise(self): s = pygame.Surface(self.rect.size, pygame.SRCALPHA) for _ in range(400): x = random.randint(0, self.rect.width - 1) y = random.randint(0, self.rect.height - 1) a = random.randint(4, 18) s.fill((255, 255, 255, a), (x, y, 1, 1)) return s def _play(self, snd): if snd: try: snd.play() except: pass def _spawn_weld_fx(self, cx, cy): for _ in range(18): self.particles.append( Particle( cx, cy, random.uniform(-3, 3), random.uniform(-3, 3), (0, 255, 245), random.randint(18, 30) ) ) for _ in range(12): self.sparks.append(Spark(cx, cy)) def handle_event(self, event): if self.finished: return if event.type == pygame.KEYDOWN: if event.key in (pygame.K_a, pygame.K_LEFT): self.offset -= 4 self._play(snd_move) elif event.key in (pygame.K_d, pygame.K_RIGHT): self.offset += 4 self._play(snd_move) elif event.key == pygame.K_SPACE and not self.welding: self.welding = True self.flash = 18 self._play(snd_weld) cx = self.rect.x + self.rect.width // 2 cy = self.rect.y + 250 self._spawn_weld_fx(cx, cy) def update(self): if self.finished: return self.offset = clamp(self.offset, -120, 120) self.power = max(0, self.power - (0.03 if not self.welding else 0.18)) if self.welding: self.flash = max(0, self.flash - 1) self.flash_alpha = int(180 * (self.flash / 18)) if self.flash > 0 else 0 if self.flash == 0: diff = abs(self.offset - self.target) if diff <= 6: self.result = "success" self.score = 100 self._play(snd_success) elif diff <= 18: self.result = "good" self.score = 75 self._play(snd_success) elif diff <= 35: self.result = "ok" self.score = 45 self._play(snd_success) else: self.result = "fail" self.score = 10 self._play(snd_fail) self.finished = True for p in self.particles: p.update() for s in self.sparks: s.update() self.particles = [p for p in self.particles if p.life > 0] self.sparks = [s for s in self.sparks if s.life > 0] def draw_background(self): gradient_background(self.screen, BG_TOP, BG_BOTTOM) # Grid lines overlay = pygame.Surface(self.rect.size, pygame.SRCALPHA) for x in range(0, self.rect.width, 40): pygame.draw.line(overlay, (50, 90, 130, 25), (x, 0), (x, self.rect.height)) for y in range(0, self.rect.height, 40): pygame.draw.line(overlay, (50, 90, 130, 18), (0, y), (self.rect.width, y)) self.screen.blit(overlay, self.rect.topleft) def draw_machine(self): x, y = self.rect.x, self.rect.y w, h = self.rect.width, self.rect.height # Outer panel draw_panel(self.screen, self.rect.inflate(-20, -20), border=BLUE, radius=24, fill=(14, 22, 36)) # Title draw_text(self.screen, "FIBER WELD SIMULATOR", x + 40, y + 30, CYAN, 34, True) draw_text(self.screen, "Оптоволоконная сварка", x + 42, y + 70, MUTED, 20) # Machine body body = pygame.Rect(x + 60, y + 120, w - 120, 280) draw_panel(self.screen, body, border=(70, 120, 190), radius=28, fill=PANEL) # Fiber guide track_y = body.centery pygame.draw.rect(self.screen, (20, 30, 48), (body.x + 80, track_y - 34, body.width - 160, 68), border_radius=20) pygame.draw.line(self.screen, (70, 120, 190), (body.x + 100, track_y), (body.right - 100, track_y), 3) # Fiber ends left_x = body.centerx - 150 + self.offset right_x = body.centerx + 150 center_x = (left_x + right_x) // 2 # Target marker target_x = body.centerx - 150 + self.target pygame.draw.line(self.screen, YELLOW, (target_x, track_y - 44), (target_x, track_y + 44), 2) draw_text(self.screen, "TARGET", target_x - 30, track_y - 70, YELLOW, 16, True) # Glass fibers self._draw_fiber(left_x, track_y, True) self._draw_fiber(right_x, track_y, False) # Welding point weld_rect = pygame.Rect(center_x - 22, track_y - 22, 44, 44) pygame.draw.ellipse(self.screen, (18, 26, 42), weld_rect) pygame.draw.ellipse(self.screen, CYAN, weld_rect, 2) draw_glow_circle(self.screen, weld_rect.center, 10, CYAN, 4) # Flash if self.flash_alpha > 0: flash = pygame.Surface(self.rect.size, pygame.SRCALPHA) pygame.draw.circle(flash, (255, 255, 255, self.flash_alpha), (center_x - x, track_y - y), 50) self.screen.blit(flash, self.rect.topleft, special_flags=pygame.BLEND_RGBA_ADD) # Stats panel stat = pygame.Rect(x + 60, y + 430, w - 120, 110) draw_panel(self.screen, stat, border=(60, 100, 160), radius=22, fill=PANEL2) diff = abs(self.offset - self.target) accuracy = clamp(100 - diff * 2, 0, 100) draw_text(self.screen, f"Смещение: {self.offset:+d}", stat.x + 24, stat.y + 18, TEXT, 24, True) draw_text(self.screen, f"Точность: {accuracy}%", stat.x + 24, stat.y + 50, GREEN if accuracy > 70 else YELLOW if accuracy > 40 else RED, 24, True) draw_text(self.screen, f"Энергия: {int(self.power)}%", stat.right - 180, stat.y + 18, CYAN, 24, True) # Help help_rect = pygame.Rect(x + 60, y + 560, w - 120, 95) draw_panel(self.screen, help_rect, border=(50, 90, 130), radius=20, fill=(16, 24, 38)) draw_text(self.screen, "A/D или ←/→ — свести волокна", help_rect.x + 24, help_rect.y + 16, TEXT, 22) draw_text(self.screen, "SPACE — выполнить сварку", help_rect.x + 24, help_rect.y + 48, CYAN, 22, True) # Progress bar bar = pygame.Rect(stat.right - 210, stat.y + 52, 170, 18) pygame.draw.rect(self.screen, (30, 40, 58), bar, border_radius=10) fill_w = int(bar.width * (self.power / 100)) pygame.draw.rect(self.screen, GREEN if self.power > 50 else YELLOW if self.power > 20 else RED, (bar.x, bar.y, fill_w, bar.height), border_radius=10) # Result if self.result: result_map = { "success": ("ОТЛИЧНО", GREEN), "good": ("ХОРОШО", CYAN), "ok": ("НОРМАЛЬНО", YELLOW), "fail": ("БРАК", RED), } txt, col = result_map[self.result] draw_text(self.screen, txt, self.rect.centerx, y + 620, col, 42, True, center=True) draw_text(self.screen, f"Нажмите R для повторной попытки", self.rect.centerx, y + 665, MUTED, 18, center=True) def _draw_fiber(self, x, y, left=True): line_color = (130, 200, 255) core_color = CYAN # Cable glow draw_glow_circle(self.screen, (x, y), 14, core_color, 5) pygame.draw.circle(self.screen, (28, 40, 60), (x, y), 16) pygame.draw.circle(self.screen, core_color, (x, y), 11) pygame.draw.circle(self.screen, WHITE, (x, y), 5) # Shaft if left: pygame.draw.line(self.screen, line_color, (x - 90, y), (x - 16, y), 6) pygame.draw.line(self.screen, (235, 245, 255), (x - 70, y), (x - 18, y), 2) else: pygame.draw.line(self.screen, line_color, (x + 16, y), (x + 90, y), 6) pygame.draw.line(self.screen, (235, 245, 255), (x + 18, y), (x + 70, y), 2) def draw_particles(self): for p in self.particles: p.draw(self.screen) for s in self.sparks: s.draw(self.screen) def draw(self): self.draw_background() self.draw_machine() self.draw_particles() # Noise overlay noise = self.noise.copy() noise.set_alpha(24) self.screen.blit(noise, self.rect.topleft) def handle_restart(self, event): if event.type == pygame.KEYDOWN and event.key == pygame.K_r: self.reset() # ----------------------------- # DEMO LOOP / USAGE # ----------------------------- def main(): screen = pygame.display.set_mode((1280, 720)) clock = pygame.time.Clock() game = FiberWeldBlock(screen, (0, 0, 1280, 720)) running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False game.handle_event(event) game.handle_restart(event) game.update() game.draw() pygame.display.flip() clock.tick(60) pygame.quit() sys.exit() if __name__ == "__main__": main()
Made on
Tilda