Загрузка данных
import pygame
import random
import sys
import json
from datetime import datetime, timedelta
CELL_SIZE = 60
MIN_SIZE, MAX_SIZE = 5, 10
WIDTH, HEIGHT = 900, 950
FPS = 15
LINE_COLORS = {
1: (255, 60, 60),
2: (60, 150, 255),
3: (60, 220, 60),
4: (255, 200, 40),
5: (190, 60, 255),
6: (255, 120, 40),
7: (40, 220, 200),
8: (255, 80, 180),
9: (140, 100, 240),
10: (200, 255, 120)
}
THEMES = {
"dark": {
"bg": (12, 12, 18),
"panel": (25, 25, 35),
"board": (18, 18, 25),
"grid": (45, 45, 60),
"text": (220, 220, 230)
},
"light": {
"bg": (220, 220, 220),
"panel": (180, 180, 180),
"board": (240, 240, 240),
"grid": (140, 140, 140),
"text": (20, 20, 20)
}
}
ACCENT = (80, 140, 255)
WIN_COLOR = (60, 255, 110)
DIM = (80, 80, 90)
pygame.init()
def get_color(idx):
return LINE_COLORS.get(idx, (100, 100, 100))
def generate_level(size, count=None):
if count is None:
count = max(3, (size * size) // 10)
mixers = []
occupied = set()
attempts = 0
while len(mixers) < count and attempts < 300:
x, y = random.randint(1, size - 2), random.randint(1, size - 2)
if (x, y) not in occupied:
c1, c2 = random.sample(range(1, 11), 2)
mixers.append({
'pos': (x, y),
'req': [c1, c2],
'filled': [False, False],
'active': False,
'type': 'mixer'
})
occupied.add((x, y))
attempts += 1
return mixers
class FlowAlchemy:
def draw_help_panel(self, screen, theme):
help_lines = [
"УПРАВЛЕНИЕ:",
"ЛКМ - рисование линии",
"ПКМ - установить источник",
"D - режим рисования",
"S - разделитель",
"E - ластик",
"N - новый уровень",
"R - перезапуск",
"+ / - изменение размера поля",
"T - смена темы",
"F1 - рекорды за день",
"F2 - рекорды за месяц",
"F3 - рекорды за год",
"Q - выход"
]
panel_x = 20
panel_y = HEIGHT - 260
pygame.draw.rect(
screen,
theme["panel"],
(panel_x, panel_y, 320, 230),
border_radius=10
)
for i, line in enumerate(help_lines):
color = WIN_COLOR if i == 0 else theme["text"]
txt = self.small.render(line, True, color)
screen.blit(
txt,
(
panel_x + 15,
panel_y + 10 + i * 16
)
)
def draw_color_help(self, screen, theme):
panel_x = 360
panel_y = HEIGHT - 260
pygame.draw.rect(
screen,
theme["panel"],
(panel_x, panel_y, 500, 230),
border_radius=10
)
title = self.small.render(
"ЦВЕТА:",
True,
WIN_COLOR
)
screen.blit(title, (panel_x + 15, panel_y + 10))
for c in range(1, 11):
row = (c - 1) // 5
col = (c - 1) % 5
x = panel_x + 40 + col * 90
y = panel_y + 60 + row * 80
pygame.draw.circle(
screen,
get_color(c),
(x, y),
18
)
key_name = str(c if c != 10 else 0)
txt = self.small.render(
f"[{key_name}]",
True,
theme["text"]
)
screen.blit(
txt,
(x - 12, y + 28)
)
def __init__(self, initial_size=10):
self.grid_size = initial_size
self.size = initial_size
self.cell = CELL_SIZE
self._calc_layout()
self.font = pygame.font.SysFont("Segoe UI", 24, bold=True)
self.small = pygame.font.SysFont("Segoe UI", 18)
self.theme = "dark"
self.stats_mode = 30
self.reset()
def _calc_layout(self):
self.width = self.size * self.cell
self.height = self.size * self.cell
self.board_x = (WIDTH - self.width) // 2
self.board_y = 90
self.bottom_y = self.board_y + self.height + 15
def reset(self, mixers=None):
self.size = self.grid_size
self._calc_layout()
self.grid = [[0] * self.size for _ in range(self.size)]
self.is_source = [[False] * self.size for _ in range(self.size)]
self.objects = [[None] * self.size for _ in range(self.size)]
self.mixers = mixers or generate_level(self.size)
for m in self.mixers:
x, y = m['pos']
self.objects[y][x] = m
self.grid[y][x] = 0
m['filled'] = [False, False]
m['active'] = False
self.selected_color = 1
self.tool = 'draw'
self.drawing = False
self.won = False
self.score = 0
self.start_time = datetime.now()
self.msg = "F1/F2/F3 - рекорды | T - тема"
self.msg_timer = 200
self.check_connections()
def save_result(self):
result = {
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"score": self.score,
"field_size": self.size
}
try:
with open("results.json", "r", encoding="utf-8") as f:
data = json.load(f)
except:
data = []
data.append(result)
with open("results.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
def load_results(self):
try:
with open("results.json", "r", encoding="utf-8") as f:
return json.load(f)
except:
return []
def get_best_results(self, days=None):
data = self.load_results()
if days is not None:
border = datetime.now() - timedelta(days=days)
filtered = []
for r in data:
d = datetime.strptime(r["date"], "%Y-%m-%d %H:%M:%S")
if d >= border:
filtered.append(r)
data = filtered
data.sort(key=lambda x: x["score"], reverse=True)
return data[:10]
def _show_msg(self, text, duration=120):
self.msg = text
self.msg_timer = duration
def _get_cell(self, pos):
mx, my = pos
if not (
self.board_x <= mx < self.board_x + self.width and
self.board_y <= my < self.board_y + self.height
):
return None
return (
(mx - self.board_x) // self.cell,
(my - self.board_y) // self.cell
)
def _cell_color(self, x, y):
obj = self.objects[y][x]
if obj and obj.get('type') == 'splitter':
return obj.get('color', 0)
return self.grid[y][x]
def _clear_color(self, color):
for y in range(self.size):
for x in range(self.size):
if self.grid[y][x] == color:
self.grid[y][x] = 0
self.is_source[y][x] = False
obj = self.objects[y][x]
if obj and obj.get('type') == 'splitter':
if obj.get('color') == color:
obj['color'] = 0
def _is_valid_placement(self, x, y, color):
if not (0 <= x < self.size and 0 <= y < self.size):
return False
obj = self.objects[y][x]
if obj and obj.get('type') == 'mixer':
return False
if self.grid[y][x] not in (0, color):
return False
return True
def check_connections(self):
for m in self.mixers:
m['filled'] = [False, False]
m['active'] = False
mx, my = m['pos']
for dx, dy in [(0,1),(0,-1),(1,0),(-1,0)]:
nx, ny = mx + dx, my + dy
if 0 <= nx < self.size and 0 <= ny < self.size:
neighbor_color = self._cell_color(nx, ny)
if neighbor_color > 0:
if neighbor_color == m['req'][0]:
m['filled'][0] = True
if neighbor_color == m['req'][1]:
m['filled'][1] = True
if m['filled'][0] and m['filled'][1]:
m['active'] = True
self.won = all(m['active'] for m in self.mixers)
if self.won:
time_bonus = max(
0,
100 - int((datetime.now() - self.start_time).seconds / 5)
)
self.score = self.size * 100 + time_bonus
self._show_msg(
f"ПОБЕДА! Очки: {self.score}",
300
)
self.save_result()
def handle_event(self, event):
cell = self._get_cell(event.pos) if event.type in (
pygame.MOUSEBUTTONDOWN,
pygame.MOUSEMOTION
) else None
if event.type == pygame.KEYDOWN:
if event.key in (
pygame.K_1, pygame.K_2, pygame.K_3,
pygame.K_4, pygame.K_5, pygame.K_6,
pygame.K_7, pygame.K_8, pygame.K_9,
pygame.K_0
):
self.selected_color = (
10 if event.key == pygame.K_0
else int(event.unicode)
)
if event.key == pygame.K_n:
self.reset(generate_level(self.size))
if event.key == pygame.K_r:
self.reset(self.mixers)
if event.key in (
pygame.K_PLUS,
pygame.K_EQUALS,
pygame.K_KP_PLUS
):
if self.grid_size < MAX_SIZE:
self.grid_size += 1
self.reset()
if event.key in (
pygame.K_MINUS,
pygame.K_UNDERSCORE,
pygame.K_KP_MINUS
):
if self.grid_size > MIN_SIZE:
self.grid_size -= 1
self.reset()
if event.key == pygame.K_d:
self.tool = 'draw'
if event.key == pygame.K_s:
self.tool = 'splitter'
if event.key == pygame.K_e:
self.tool = 'eraser'
if event.key == pygame.K_t:
self.theme = (
"light"
if self.theme == "dark"
else "dark"
)
if event.key == pygame.K_F1:
self.stats_mode = 1
if event.key == pygame.K_F2:
self.stats_mode = 30
if event.key == pygame.K_F3:
self.stats_mode = 365
if event.key == pygame.K_q:
pygame.quit()
sys.exit()
if event.key == pygame.K_i:
self.won = True
if cell:
cx, cy = cell
obj_at_cell = self.objects[cy][cx]
if (
event.type == pygame.MOUSEBUTTONDOWN
and event.button == 3
and self.tool == 'draw'
):
if not (
obj_at_cell and
obj_at_cell.get('type') == 'mixer'
):
self._clear_color(self.selected_color)
self.grid[cy][cx] = self.selected_color
self.is_source[cy][cx] = True
self.objects[cy][cx] = None
self.check_connections()
elif (
event.type == pygame.MOUSEBUTTONDOWN
and event.button == 1
):
if self.tool == 'draw':
if self._cell_color(cx, cy) == self.selected_color:
self.drawing = True
self.last_cell = (cx, cy)
elif self.tool == 'splitter':
if self.grid[cy][cx] == 0 and not obj_at_cell:
self.objects[cy][cx] = {
'type': 'splitter',
'color': 0
}
elif self.tool == 'eraser':
if obj_at_cell and obj_at_cell.get('type') == 'mixer':
return
self.grid[cy][cx] = 0
self.is_source[cy][cx] = False
if (
obj_at_cell and
obj_at_cell.get('type') == 'splitter'
):
self.objects[cy][cx] = None
self.check_connections()
elif (
event.type == pygame.MOUSEMOTION
and self.drawing
and pygame.mouse.get_pressed()[0]
):
if self._is_valid_placement(
cx,
cy,
self.selected_color
):
dx = abs(cx - self.last_cell[0])
dy = abs(cy - self.last_cell[1])
if dx + dy == 1:
self.grid[cy][cx] = self.selected_color
if (
obj_at_cell and
obj_at_cell.get('type') == 'splitter'
):
obj_at_cell['color'] = self.selected_color
self.last_cell = (cx, cy)
self.check_connections()
if event.type == pygame.MOUSEBUTTONUP:
self.drawing = False
def draw(self, screen):
theme = THEMES[self.theme]
screen.fill(theme["bg"])
pygame.draw.rect(
screen,
theme["panel"],
(0, 0, WIDTH, self.board_y - 10)
)
pygame.draw.rect(
screen,
ACCENT,
(20, self.board_y - 15, WIDTH - 40, 3),
border_radius=2
)
title = self.font.render(
"Алхимия потоков",
True,
theme["text"]
)
screen.blit(
title,
(
WIDTH // 2 - title.get_width() // 2,
15
)
)
active = sum(1 for m in self.mixers if m['active'])
status = (
f"Активировано: {active}/{len(self.mixers)} "
f"| Поле: {self.size}x{self.size}"
)
st = self.small.render(status, True, theme["text"])
screen.blit(
st,
(
WIDTH // 2 - st.get_width() // 2,
45
)
)
score_text = self.small.render(
f"Очки: {self.score}",
True,
WIN_COLOR
)
screen.blit(score_text, (20, 20))
pygame.draw.rect(
screen,
theme["board"],
(
self.board_x,
self.board_y,
self.width,
self.height
),
border_radius=6
)
for i in range(1, self.size):
pygame.draw.line(
screen,
theme["grid"],
(
self.board_x + i * self.cell,
self.board_y
),
(
self.board_x + i * self.cell,
self.board_y + self.height
),
1
)
pygame.draw.line(
screen,
theme["grid"],
(
self.board_x,
self.board_y + i * self.cell
),
(
self.board_x + self.width,
self.board_y + i * self.cell
),
1
)
for y in range(self.size):
for x in range(self.size):
c = self._cell_color(x, y)
if c > 0:
for dx, dy in [(0,1),(1,0)]:
nx, ny = x + dx, y + dy
if (
0 <= nx < self.size and
0 <= ny < self.size and
self._cell_color(nx, ny) == c
):
p1 = (
x * self.cell + self.cell // 2 + self.board_x,
y * self.cell + self.cell // 2 + self.board_y
)
p2 = (
nx * self.cell + self.cell // 2 + self.board_x,
ny * self.cell + self.cell // 2 + self.board_y
)
pygame.draw.line(
screen,
get_color(c),
p1,
p2,
self.cell - 16
)
for y in range(self.size):
for x in range(self.size):
cx = x * self.cell + self.cell // 2 + self.board_x
cy = y * self.cell + self.cell // 2 + self.board_y
obj = self.objects[y][x]
if self.is_source[y][x]:
pygame.draw.circle(
screen,
get_color(self.grid[y][x]),
(cx, cy),
self.cell // 3
)
pygame.draw.circle(
screen,
(255,255,255),
(cx, cy),
self.cell // 3,
3
)
elif obj:
if obj['type'] == 'mixer':
bg = (
(20,140,60)
if obj['active']
else (25,30,35)
)
pygame.draw.rect(
screen,
bg,
(cx-24, cy-24, 48, 48),
border_radius=10
)
for i, rc in enumerate(obj['req']):
px, py = (
(cx-12, cy-12)
if i == 0
else (cx+12, cy+12)
)
pygame.draw.circle(
screen,
get_color(rc),
(px, py),
8
)
elif obj['type'] == 'splitter':
col = (
get_color(obj['color'])
if obj['color'] > 0
else (90,95,110)
)
pygame.draw.polygon(
screen,
col,
[
(cx, cy-18),
(cx+18, cy),
(cx, cy+18),
(cx-18, cy)
]
)
results = self.get_best_results(self.stats_mode)
titles = {
1: "ТОП за день",
30: "ТОП за месяц",
365: "ТОП за год"
}
rx = WIDTH - 250
ry = 120
title = self.small.render(
titles[self.stats_mode],
True,
theme["text"]
)
screen.blit(title, (rx, ry))
for i, r in enumerate(results[:5]):
txt = (
f"{i+1}. "
f"{r['score']} очк."
)
surf = self.small.render(
txt,
True,
theme["text"]
)
screen.blit(
surf,
(
rx,
ry + 30 + i * 25
)
)
if self.msg and self.msg_timer > 0:
surf = self.small.render(
self.msg,
True,
WIN_COLOR
)
screen.blit(
surf,
(
WIDTH // 2 - surf.get_width() // 2,
HEIGHT - 30
)
)
self.msg_timer -= 1
elapsed = (datetime.now() - self.start_time).seconds
finaltime = elapsed
minutes = elapsed // 60
seconds = elapsed % 60
pygame.display.set_caption(
f"Алхимия потоков | Время: {minutes:02}:{seconds:02}"
)
if self.won:
elapsed = finaltime
minutes = elapsed // 60
seconds = elapsed % 60
pygame.display.set_caption(
#f"Алхимия потоков | Время: {minutes:02}:{seconds:02}"
f"Вы победили {minutes:02}:{seconds:02}"
)
self.draw_help_panel(screen, theme)
self.draw_color_help(screen, theme)
pygame.display.flip()
def main():
screen = pygame.display.set_mode(
(WIDTH, HEIGHT)
)
pygame.display.set_caption("Алхимия потоков")
clock = pygame.time.Clock()
game = FlowAlchemy(10)
while True:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
game.handle_event(event)
game.draw(screen)
if __name__ == "__main__":
main()