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()