Загрузка данных
import pygame
import sys
import random
import math
from collections import deque
import json
import os
RECORDS_FILE = "survival_records.json"
def load_records():
"""Загружает рекорды из файла. Если файла нет, возвращает словарь с нулями."""
if os.path.exists(RECORDS_FILE):
try:
with open(RECORDS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except:
pass
# Значения по умолчанию
return {
"snakes": 0,
"spiders": 0,
"slimes": 0,
"insects": 0,
"fish": 0
}
def save_records(records):
"""Сохраняет рекорды в файл."""
with open(RECORDS_FILE, 'w', encoding='utf-8') as f:
json.dump(records, f, ensure_ascii=False, indent=2)
# Загружаем рекорды при старте (глобально или в каждой функции)
survival_records = load_records()
# Рнициализация Pygame
pygame.init()
# Константы
GRID_SIZE = 17
CELL_SIZE = 45
WIDTH = GRID_SIZE * CELL_SIZE
HEIGHT = GRID_SIZE * CELL_SIZE
FPS = 30
# Цвета
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (180, 180, 180)
LIGHT_GRAY = (220, 220, 220)
BUTTON_COLOR = (100, 100, 255)
BUTTON_HOVER_COLOR = (150, 150, 255)
TEXT_COLOR = (230, 230, 230)
MENU_BG = (240, 240, 245)
# Цвета змеек
GREEN = (0, 255, 0)
DARK_GREEN = (0, 150, 0)
DARK_GREEN_PURPLE = (0, 100, 0)
PURPLE_SPOT = (128, 0, 128)
BRIGHT_GREEN = (50, 255, 50)
YELLOW_STRIPE = (255, 255, 0)
RED_SNAKE = (255, 0, 0)
DARK_RED_SNAKE = (139, 0, 0)
BLUE = (0, 0, 255)
DARK_BLUE = (0, 0, 139)
DARK_BLUE_PURPLE = (0, 0, 100)
PURPLE_DOT = (138, 43, 226)
LIGHT_BLUE = (100, 100, 255)
CYAN_STRIPE = (0, 255, 255)
BLACK_SNAKE = (20, 20, 20)
DARK_GRAY_BODY = (60, 60, 60)
RED_EYE = (255, 0, 0)
ORANGE_STRIPE = (255, 165, 0)
BRIGHT_ORANGE_STRIPE = (255, 100, 0)
SWAMP_GREEN = (85, 107, 47)
PINK_AZURE = (255, 105, 180)
WHITE_SNAKE = (240, 240, 240)
WHITE_PURPLE_STRIPE = (200, 150, 255)
WHITE_RED_SPOT = (255, 200, 200)
PURPLE_FIRE = (255, 0, 255)
# Цвета для пауков
SPIDER_LIGHT_GRAY = (200, 200, 200)
SPIDER_BROWN_GRAY = (139, 69, 19)
SPIDER_DARK_GRAY = (70, 70, 70)
SPIDER_RED_CROSS = (255, 0, 0)
SPIDER_GREEN_CIRCLE = (0, 255, 0)
SPIDER_DARK_RED = (139, 0, 0)
SPIDER_BIG_DARK_GRAY = (50, 50, 50)
SPIDER_BLACK = (0, 0, 0)
SPIDER_WHITE_PURPLE = (240, 240, 240)
LIGHT_BLUE_CELL = (173, 216, 230)
LIGHT_PINK_CELL = (255, 182, 193)
# Цвета для насекомых и песка
SAND_INSECT_LIGHT = (245, 222, 179)
SAND_INSECT_DARK = (210, 180, 140)
FISH_LIGHT_BLUE = (135, 206, 250)
FISH_DARK_BLUE = (70, 130, 180)
ANT_COLOR = (139, 69, 19)
DUNG_BEETLE_COLOR = (85, 107, 47)
STAG_BEETLE_COLOR = (60, 60, 60)
ANTHILL_COLOR = (101, 67, 33)
POISON_COLOR = (100, 200, 100)
# Цвета для воды (режим рыб)
WATER_DEEP = (30, 80, 140) # больше не используется, заменён на FISH_*
WATER_SHALLOW = (60, 140, 210)
# Русские названия змей (глобально)
RU_NAMES_SNAKES = {
"green": "Зелёная",
"dark_green": "Фиолетовая",
"bright_green": "Быстрая",
"stripe_green": "Стреляющая зелёная",
"swamp_green": "Болотная",
"blue": "РЎРёРЅСЏСЏ",
"dark_blue": "Тёмно-синяя",
"light_blue": "Светло-синяя",
"stripe_blue": "Полосатая синяя",
"pink_azure": "Розово-лазурная",
"white": "Белая",
"white_purple": "Бело-фиолетовая",
"white_red": "Бело-красная",
"red": "Красная",
"black": "Чёрная",
"rainbow_snake": "Радужная",
"white_gray": "Бело-серая"
}
FOG_RADIUS = 7 # радиус видимости в клетках для модификатора "туман"
# Настройка окна
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Птичка против Врагов")
clock = pygame.time.Clock()
font = pygame.font.Font(None, 36)
small_font = pygame.font.Font(None, 24)
tiny_font = pygame.font.Font(None, 20)
micro_font = pygame.font.Font(None, 18)
fullscreen = False
bird_skin = 0 # 0-серый, 1-синий, 2-красный, 3-жёлтый, 4-чёрный
def draw_half_circle(surface, color, center, radius, direction, border_color=BLACK, border_width=2):
"""
Рисует полукруг, направленный выпуклой частью по вектору direction.
direction: кортеж (dx, dy), где dx, dy — целые числа (-1,0,1).
"""
dx, dy = direction
# Угол направления движения (в радианах)
angle = math.atan2(dy, dx)
# Полукруг от -90° до +90° относительно направления
start_angle = angle - math.pi / 2
end_angle = angle + math.pi / 2
# Количество точек на дуге
num_points = 20
points = [center]
for i in range(num_points + 1):
t = start_angle + (end_angle - start_angle) * i / num_points
x = center[0] + radius * math.cos(t)
y = center[1] + radius * math.sin(t)
points.append((x, y))
# Рисуем залитый полигон
if len(points) > 2:
pygame.draw.polygon(surface, color, points)
if border_width > 0:
pygame.draw.polygon(surface, border_color, points, border_width)
def draw_droplet(surface, color, center, radius, direction, border_color=BLACK, border_width=2):
"""
Рисует каплю, направленную остриём по вектору direction.
direction: кортеж (dx, dy) - направление движения.
"""
dx, dy = direction
angle = math.atan2(dy, dx) # угол направления
# Параметры капли
head_radius = radius
tail_length = radius * 1.2 # длина хвоста
tail_width = radius * 0.6 # ширина хвоста у основания
# Центр головы капли (смещён немного назад, чтобы хвост был сзади)
head_center_offset = tail_length * 0.3
head_cx = center[0] - dx * head_center_offset
head_cy = center[1] - dy * head_center_offset
# Точки для полигона капли
points = []
# Полукруг спереди (от -90° до +90° относительно направления)
num_points = 12
start_angle = angle - math.pi / 2
end_angle = angle + math.pi / 2
for i in range(num_points + 1):
t = start_angle + (end_angle - start_angle) * i / num_points
x = head_cx + head_radius * math.cos(t)
y = head_cy + head_radius * math.sin(t)
points.append((x, y))
# Хвост: сужающаяся часть сзади
# Правая точка основания хвоста
tail_base_rx = head_cx + head_radius * math.cos(angle - math.pi / 2)
tail_base_ry = head_cy + head_radius * math.sin(angle - math.pi / 2)
# Левая точка основания хвоста
tail_base_lx = head_cx + head_radius * math.cos(angle + math.pi / 2)
tail_base_ly = head_cy + head_radius * math.sin(angle + math.pi / 2)
# Остриё хвоста
tail_tip_x = head_cx - dx * tail_length
tail_tip_y = head_cy - dy * tail_length
# Добавляем остриё и возвращаемся к началу полукруга (в обратном порядке)
points.append((tail_tip_x, tail_tip_y))
# Рисуем залитый полигон
if len(points) > 2:
pygame.draw.polygon(surface, color, points)
if border_width > 0:
pygame.draw.polygon(surface, border_color, points, border_width)
# Белая точка в центре головы (как у полукруга)
dot_radius = radius // 2
pygame.draw.circle(surface, WHITE, (int(head_cx), int(head_cy)), dot_radius)
pygame.draw.circle(surface, BLACK, (int(head_cx), int(head_cy)), dot_radius, 1)
def rotate_point(x, y, angle, cx=0, cy=0):
"""Поворачивает точку (x,y) вокруг (cx,cy) на угол angle в радианах."""
cos_a = math.cos(angle)
sin_a = math.sin(angle)
nx = cx + (x - cx) * cos_a - (y - cy) * sin_a
ny = cy + (x - cx) * sin_a + (y - cy) * cos_a
return nx, ny
# ---------------------- РљРќРћРџРљРђ ----------------------
class Button:
def __init__(self, x, y, width, height, text, action=None, color=BUTTON_COLOR, hover_color=BUTTON_HOVER_COLOR,
text_color=TEXT_COLOR, font=None):
self.rect = pygame.Rect(x, y, width, height)
self.text = text
self.action = action
self.color = color
self.hover_color = hover_color
self.text_color = text_color
self.font = font if font else pygame.font.Font(None, 36)
def draw(self, surface):
mouse_pos = pygame.mouse.get_pos()
if self.rect.collidepoint(mouse_pos):
pygame.draw.rect(surface, self.hover_color, self.rect, border_radius=5)
else:
pygame.draw.rect(surface, self.color, self.rect, border_radius=5)
pygame.draw.rect(surface, BLACK, self.rect, 2, border_radius=5)
text_surf = self.font.render(self.text, True, self.text_color)
text_rect = text_surf.get_rect(center=self.rect.center)
surface.blit(text_surf, text_rect)
def handle_event(self, event):
if event.type == pygame.MOUSEBUTTONDOWN:
if self.rect.collidepoint(event.pos):
if self.action:
self.action()
# ---------------------- РћРўР РРЎРћР’РљРђ РџРўРР§РљР ----------------------
def draw_bird(screen, x, y, visible=True, black_shadow=False):
if not visible:
return
center_x = x * CELL_SIZE + CELL_SIZE // 2
center_y = y * CELL_SIZE + CELL_SIZE // 2
radius = CELL_SIZE // 3
if black_shadow:
pygame.draw.circle(screen, BLACK, (center_x, center_y), radius)
return
if bird_skin == 0:
body_color = (80, 80, 80)
wing_color = (60, 60, 60)
beak_color = (255, 200, 0)
elif bird_skin == 1:
body_color = (70, 130, 180)
wing_color = (50, 100, 150)
beak_color = (255, 215, 0)
elif bird_skin == 2:
body_color = (120, 60, 60)
wing_color = (90, 40, 40)
beak_color = (255, 100, 100)
elif bird_skin == 3:
body_color = (255, 255, 0)
wing_color = (200, 200, 0)
beak_color = (255, 140, 0)
else:
body_color = (30, 30, 30)
wing_color = (10, 10, 10)
beak_color = (200, 200, 200)
pygame.draw.circle(screen, body_color, (center_x, center_y), radius)
pygame.draw.circle(screen, WHITE, (center_x + radius // 3, center_y - radius // 3), radius // 4)
pygame.draw.circle(screen, BLACK, (center_x + radius // 3, center_y - radius // 3), radius // 8)
pygame.draw.polygon(screen, beak_color, [
(center_x + radius // 2, center_y),
(center_x + radius, center_y - radius // 6),
(center_x + radius, center_y + radius // 6)
])
pygame.draw.ellipse(screen, wing_color, (center_x - radius, center_y, radius, radius // 2))
# ---------------------- ЗМЕР----------------------
SNAKE_INFO_ORDERED = [
"green", "dark_green", "bright_green", "stripe_green", "swamp_green",
"blue", "dark_blue", "light_blue", "stripe_blue", "pink_azure",
"white", "white_purple", "white_red",
"red", "black", "rainbow_snake", "white_gray" # добавлена бело-серая
]
TAB_CATEGORIES_SNAKES = {
"Зелёные": ["green", "dark_green", "bright_green", "stripe_green", "swamp_green"],
"РЎРёРЅРёРµ": ["blue", "dark_blue", "light_blue", "stripe_blue", "pink_azure"],
"Белые": ["white", "white_purple", "white_red", "white_gray"],
"РњРёРЅРёР±РѕСЃСЃС‹": ["red", "black"],
"Радужные": ["rainbow_snake"]
}
TAB_NAMES_SNAKES = ["Зелёные", "Синие", "Белые", "Минибоссы", "Радужные"]
SNAKE_INFO = {
"green": {
"description": "Стандартная змейка. Средняя скорость и длина.",
"length_range": "4-6", "speed": 1, "special": "Нет",
"color": GREEN, "head_color": DARK_GREEN, "body_color": GREEN, "width": 1
},
"dark_green": {
"description": "Хитрая змейка, умеет резко менять направление.",
"length_range": "4-8", "speed": 1, "special": "Повороты: 2",
"color": DARK_GREEN_PURPLE, "head_color": DARK_GREEN_PURPLE, "body_color": DARK_GREEN_PURPLE, "width": 1
},
"bright_green": {
"description": "Быстрая змейка, движется в два раза быстрее.",
"length_range": "3-5", "speed": 2, "special": "Нет",
"color": BRIGHT_GREEN, "head_color": BRIGHT_GREEN, "body_color": BRIGHT_GREEN, "width": 1
},
"stripe_green": {
"description": "Стреляет огненными шарами раз в 5 ходов вправо и влево.",
"length_range": "2-3", "speed": 1, "special": "Стрельба",
"color": GREEN, "head_color": DARK_GREEN, "body_color": GREEN, "width": 1
},
"swamp_green": {
"description": "Двигается рывками: 2 хода стоит, затем прыжок на 3 клетки.",
"length_range": "6-7", "speed": "рывок", "special": "Рывок через ход",
"color": SWAMP_GREEN, "head_color": SWAMP_GREEN, "body_color": (60, 80, 30), "width": 1
},
"blue": {
"description": "Длинная и медленная змейка, заполняет поле.",
"length_range": "5-10", "speed": 1, "special": "Нет",
"color": BLUE, "head_color": DARK_BLUE, "body_color": BLUE, "width": 1
},
"dark_blue": {
"description": "Очень хитрая и длинная, часто меняет направление.",
"length_range": "4-9", "speed": 1, "special": "Повороты: 5",
"color": DARK_BLUE_PURPLE, "head_color": DARK_BLUE_PURPLE, "body_color": DARK_BLUE_PURPLE, "width": 1
},
"light_blue": {
"description": "Сверхбыстрая змейка, уворачивается от всего.",
"length_range": "3-4", "speed": 3, "special": "Нет",
"color": LIGHT_BLUE, "head_color": LIGHT_BLUE, "body_color": LIGHT_BLUE, "width": 1
},
"stripe_blue": {
"description": "Стреляет огненными шарами раз в 3 хода вправо и влево.",
"length_range": "2-4", "speed": 1, "special": "Частая стрельба",
"color": BLUE, "head_color": DARK_BLUE, "body_color": BLUE, "width": 1
},
"pink_azure": {
"description": "Двигается рывками: 2 хода стоит, затем прыжок на 4 клетки.",
"length_range": "7-8", "speed": "рывок", "special": "Рывок на 4 клетки",
"color": PINK_AZURE, "head_color": PINK_AZURE, "body_color": (200, 80, 140), "width": 1
},
"white": {
"description": "Белая змея, заменяет синих после 2 чёрных.",
"length_range": "7-12", "speed": 2, "special": "Повороты: 1",
"color": WHITE_SNAKE, "head_color": (200, 200, 200), "body_color": WHITE_SNAKE, "width": 1
},
"white_purple": {
"description": "Белая с фиолетовыми полосками, заменяет стреляющих после 3 чёрных.",
"length_range": "8-13", "speed": 1, "special": "Повороты: 7",
"color": WHITE_PURPLE_STRIPE, "head_color": (180, 130, 230), "body_color": WHITE_SNAKE, "width": 1
},
"white_red": {
"description": "Белая с красными пятнами, стреляет полосами фиолетового огня (длина 3, скорость 2).",
"length_range": "4-5", "speed": 1, "special": "Полосы 3 кл., скор. 2",
"color": WHITE_RED_SPOT, "head_color": (255, 150, 150), "body_color": WHITE_SNAKE, "width": 1
},
"white_gray": {
"description": "Бело-серая змея. Каждый ход создаёт невидимое облако вокруг головы (радиус 2-3, 3 хода).",
"length_range": "5-7", "speed": 1, "special": "Невидимый газ, 3 поворота", "spawn_condition": "После 5 чёрных",
"color": (200, 200, 200), "head_color": (160, 160, 160), "body_color": (180, 180, 180), "width": 1
},
"red": {
"description": "Опасная, большая змейка, появляется редко, но метко. Стреляет огненными шарами.",
"length_range": "14-16", "speed": 1, "special": "Ширина: 2, стрельба",
"color": RED_SNAKE, "head_color": DARK_RED_SNAKE, "body_color": RED_SNAKE, "width": 2
},
"black": {
"description": "Самая большая и смертоносная, медленно, но верно уничтожает. Стреляет двумя фиолетовыми шарами.",
"length_range": "30", "speed": 2, "special": "РЁРёСЂРёРЅР°: 3",
"color": BLACK_SNAKE, "head_color": BLACK_SNAKE, "body_color": DARK_GRAY_BODY, "width": 3
},
"rainbow_snake": {
"description": "Радужная змея. Редчайшая. Раз в 3 хода делает рывок на 4 клетки, может дважды повернуть.",
"length_range": "17", "speed": 1, "special": "Рывок раз в 3 хода, 2 поворота",
"color": (255, 0, 255), "head_color": (255, 0, 255), "body_color": (200, 0, 200), "width": 2, "rainbow": True
}
}
LEVEL_INFO = {
# Глава 1: Змеи (уровни 1-10)
1: {"target_type": "kill", "target_kills": 20,
"initial_enemies": [("snake", "green", 1)],
"spawn_pool": ["green"], "max_enemies": 10,
"modifiers": {}, "equipment": []},
2: {"target_type": "collect_apples", "target_apples": 5,
"initial_enemies": [("snake", "green", 1), ("snake", "swamp_green", 1)],
"spawn_pool": ["green", "swamp_green"], "max_enemies": 10,
"modifiers": {}, "equipment": []},
3: {"target_type": "survive_turns", "target_turns": 75,
"initial_enemies": [("snake", "bright_green", 2)],
"spawn_pool": ["bright_green"], "max_enemies": 8,
"modifiers": {}, "equipment": []},
4: {"target_type": "collect_apples", "target_apples": 7,
"initial_enemies": [("snake", "dark_green", 2)],
"spawn_pool": ["dark_green"], "max_enemies": 10,
"modifiers": {}, "equipment": ["Метод скипа"]},
5: {"target_type": "kill", "target_kills": 50,
"initial_enemies": [("snake", "stripe_green", 2), ("snake", "bright_green", 2), ("snake", "dark_green", 2)],
"spawn_pool": ["stripe_green", "bright_green", "dark_green"], "max_enemies": 12,
"modifiers": {}, "equipment": []},
6: {"target_type": "collect_apples", "target_apples": 10,
"initial_enemies": [("snake", "green", 2), ("snake", "swamp_green", 1), ("snake", "stripe_green", 1)],
"spawn_pool": ["green", "swamp_green", "stripe_green"], "max_enemies": 12,
"modifiers": {}, "equipment": ["Мощные крылья"]},
7: {"target_type": "survive_turns", "target_turns": 150,
"initial_enemies": [("snake", "stripe_green", 3)],
"spawn_pool": ["stripe_green"], "max_enemies": 10,
"modifiers": {"god_of_torches": True}, "equipment": ["Айсаир"]},
8: {"target_type": "collect_apples", "target_apples": 10,
"initial_enemies": [("snake", "green", 1), ("snake", "swamp_green", 1), ("snake", "bright_green", 1),
("snake", "dark_green", 1), ("snake", "stripe_green", 1)],
"spawn_pool": ["green", "swamp_green", "bright_green", "dark_green", "stripe_green"], "max_enemies": 12,
"modifiers": {}, "equipment": []},
9: {"target_type": "kill", "target_kills": 75,
"initial_enemies": [("snake", "green", 2), ("snake", "swamp_green", 2), ("snake", "bright_green", 2),
("snake", "dark_green", 2), ("snake", "stripe_green", 2)],
"spawn_pool": ["green", "swamp_green", "bright_green", "dark_green", "stripe_green"], "max_enemies": 14,
"modifiers": {}, "equipment": ["Метод скипа", "Мощные крылья", "Айсаир"]},
10: {"target_type": "kill_and_collect", "target_kills": 15, "target_apples": 5,
"initial_enemies": [("snake", "red", 2)],
"spawn_pool": ["red"], "max_enemies": 4,
"modifiers": {}, "equipment": []},
# Глава 2: Маленькие пауки (уровни 11-20)
11: {"enemy_type": "spider", "arena_style": 1,
"target_type": "kill", "target_kills": 30,
"initial_enemies": [("spider", "light_gray", 2), ("spider", "brown_gray", 2)],
"spawn_pool": ["light_gray", "brown_gray"], "max_enemies": 10,
"modifiers": {}, "equipment": ["Метод скипа"]},
12: {"enemy_type": "spider", "arena_style": 1,
"target_type": "visit_borders",
"initial_enemies": [("spider", "light_gray", 2), ("spider", "brown_gray", 2),
("spider", "dark_gray_green_circle", 1)],
"spawn_pool": ["light_gray", "brown_gray", "dark_gray_green_circle"], "max_enemies": 10,
"modifiers": {}, "equipment": []},
13: {"enemy_type": "spider", "arena_style": 1,
"target_type": "kill", "target_kills": 25,
"initial_enemies": [("spider", "dark_gray_green_circle", 3)],
"spawn_pool": ["dark_gray_green_circle"], "max_enemies": 8,
"modifiers": {}, "equipment": ["Освежитель"]},
14: {"enemy_type": "spider", "arena_style": 1,
"target_type": "survive_turns", "target_turns": 100,
"initial_enemies": [("spider", "dark_gray_red_cross", 3)],
"spawn_pool": ["dark_gray_red_cross"], "max_enemies": 8,
"modifiers": {"walls_are_lava": True}, "equipment": ["Мощные крылья"]},
15: {"enemy_type": "spider", "arena_style": 1,
"target_type": "survive_turns", "target_turns": 100,
"initial_enemies": [("spider", "dark_gray_red_cross", 2), ("spider", "dark_gray_green_circle", 2),
("spider", "brown_gray", 2)],
"spawn_pool": ["dark_gray_red_cross", "dark_gray_green_circle", "brown_gray"], "max_enemies": 10,
"modifiers": {"walls_are_lava": True}, "equipment": ["Мощные крылья"]},
16: {"enemy_type": "spider", "arena_style": 1,
"target_type": "visit_borders",
"initial_enemies": [("spider", "white_purple_circle", 2)],
"spawn_pool": ["white_purple_circle"], "max_enemies": 8,
"modifiers": {}, "equipment": []},
17: {"enemy_type": "spider", "arena_style": 1,
"target_type": "survive_turns", "target_turns": 150,
"initial_enemies": [("spider", "white_purple_circle", 2), ("spider", "dark_gray_red_cross", 2), ("spider", "brown_gray", 2)],
"spawn_pool": ["white_purple_circle", "dark_gray_red_cross", "brown_gray"], "max_enemies": 10,
"modifiers": {"fog": True}, "equipment": []},
18: {"enemy_type": "spider", "arena_style": 1,
"target_type": "visit_corners",
"initial_enemies": [("spider", "light_gray", 2), ("spider", "brown_gray", 2),
("spider", "dark_gray_red_cross", 2), ("spider", "dark_gray_green_circle", 2),
("spider", "white_purple_circle", 2)],
"spawn_pool": ["light_gray", "brown_gray", "dark_gray_red_cross", "dark_gray_green_circle", "white_purple_circle"],
"max_enemies": 14,
"modifiers": {}, "equipment": ["Освежитель", "Мощные крылья", "Метод скипа"]},
19: {"enemy_type": "spider", "arena_style": 1,
"target_type": "kill", "target_kills": 75,
"initial_enemies": [("spider", "light_gray", 2), ("spider", "brown_gray", 2),
("spider", "dark_gray_red_cross", 2), ("spider", "dark_gray_green_circle", 2),
("spider", "white_purple_circle", 2)],
"spawn_pool": ["light_gray", "brown_gray", "dark_gray_red_cross", "dark_gray_green_circle", "white_purple_circle"],
"max_enemies": 14,
"modifiers": {}, "equipment": ["Освежитель", "Мощные крылья", "Метод скипа"]},
20: {"enemy_type": "spider", "arena_style": 1,
"target_type": "kill", "target_kills": 17,
"initial_enemies": [("spider", "dark_red", 2)],
"spawn_pool": ["dark_red"], "max_enemies": 4,
"modifiers": {"fog": True, "walls_are_lava": True}, "equipment": []},
}
class Fireball:
def __init__(self, x, y, dx, dy, speed=1, color=(255, 69, 0)):
self.x = x
self.y = y
self.dx = dx
self.dy = dy
self.speed = speed
self.active = True
self.color = color
self.move_timer = 0 # <-- добавлено
def move(self):
if self.speed < 1.0:
self.move_timer += 1
if self.move_timer >= int(1 / self.speed):
self.move_timer = 0
self._move_step()
else:
for _ in range(int(self.speed)):
self._move_step()
if not self.active:
break
def _move_step(self):
self.x += self.dx
self.y += self.dy
if not (0 <= self.x < GRID_SIZE and 0 <= self.y < GRID_SIZE):
self.active = False
def draw(self, screen):
if self.active:
rect = pygame.Rect(self.x * CELL_SIZE, self.y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
center = rect.center
radius = CELL_SIZE // 3
draw_droplet(screen, self.color, center, radius, (self.dx, self.dy), border_color=BLACK, border_width=2)
def get_pos(self):
return (self.x, self.y)
class Snake:
def __init__(self, start_x, start_y, length, direction, snake_type='green', width=1):
self.length = length
self.body = deque()
self.direction = direction
self.snake_type = snake_type
self.width = width
self.turns_left = 0
self.move_speed = 1
self.rainbow_offset = 0
self.shoot_cooldown = 0
self.shoot_delay = 0
if snake_type == 'dark_green':
self.turns_left = 2
elif snake_type == 'dark_blue':
self.turns_left = 5
elif snake_type == 'bright_green':
self.move_speed = 2
elif snake_type == 'light_blue':
self.move_speed = 3
elif snake_type == 'black':
self.move_speed = 2
elif snake_type == 'white':
self.turns_left = 1
self.move_speed = 2
elif snake_type == 'white_purple':
self.turns_left = 7
self.move_speed = 1
elif snake_type == 'white_red':
self.shoot_delay = 3
self.move_speed = 1
elif snake_type == 'stripe_green':
self.shoot_delay = 5
elif snake_type == 'stripe_blue':
self.shoot_delay = 3
elif snake_type == 'swamp_green':
self.charge = 0
self.charge_timer = 0
self.rush_distance = 3
elif snake_type == 'pink_azure':
self.charge = 0
self.charge_timer = 0
self.rush_distance = 4
elif snake_type == 'rainbow_snake':
self.turns_left = 2
self.move_speed = 1
self.rush_cooldown = 0
self.rush_delay = 3
self.rush_distance = 4
elif snake_type == 'red':
self.shoot_delay = 5 # красная стреляет огненными шарами
elif snake_type == 'white_gray':
self.turns_left = 3
self.move_speed = 1
self.gas_cooldown = 0
self.create_snake_at_edge(start_x, start_y)
def create_snake_at_edge(self, start_x, start_y):
if self.width == 3:
if self.direction == 'down':
for i in range(self.length):
self.body.append(((start_x, start_y - i), (start_x + 1, start_y - i), (start_x + 2, start_y - i)))
elif self.direction == 'up':
for i in range(self.length):
self.body.append(((start_x, start_y + i), (start_x + 1, start_y + i), (start_x + 2, start_y + i)))
elif self.direction == 'right':
for i in range(self.length):
self.body.append(((start_x - i, start_y), (start_x - i, start_y + 1), (start_x - i, start_y + 2)))
else:
for i in range(self.length):
self.body.append(((start_x + i, start_y), (start_x + i, start_y + 1), (start_x + i, start_y + 2)))
elif self.width == 2:
if self.direction == 'down':
for i in range(self.length):
self.body.append(((start_x, start_y - i), (start_x + 1, start_y - i)))
elif self.direction == 'up':
for i in range(self.length):
self.body.append(((start_x, start_y + i), (start_x + 1, start_y + i)))
elif self.direction == 'right':
for i in range(self.length):
self.body.append(((start_x - i, start_y), (start_x - i, start_y + 1)))
else:
for i in range(self.length):
self.body.append(((start_x + i, start_y), (start_x + i, start_y + 1)))
else:
if self.direction == 'down':
for i in range(self.length):
self.body.append((start_x, start_y - i))
elif self.direction == 'up':
for i in range(self.length):
self.body.append((start_x, start_y + i))
elif self.direction == 'right':
for i in range(self.length):
self.body.append((start_x - i, start_y))
else:
for i in range(self.length):
self.body.append((start_x + i, start_y))
def calculate_direction_to_player(self, bird_x, bird_y):
if not self.body:
return self.direction
if self.width == 1:
head_x, head_y = self.body[0]
else:
head_x = self.body[0][0][0]
head_y = self.body[0][0][1]
dx = bird_x - head_x
dy = bird_y - head_y
if abs(dx) > abs(dy):
return 'right' if dx > 0 else 'left'
else:
return 'down' if dy > 0 else 'up'
# В методе shoot класса Snake (замените существующий)
def shoot(self, fireballs):
# Определяем клетки головы
if self.width == 1:
head_cells = [self.body[0]]
else:
head_cells = list(self.body[0]) # кортеж клеток для ширины 2 или 3
# Красная змея: стреляет из каждой клетки головы
if self.snake_type == 'red':
color = (255, 69, 0)
for (hx, hy) in head_cells:
if self.direction in ('left', 'right'):
fireballs.append(Fireball(hx, hy - 1, 0, -1, color=color))
fireballs.append(Fireball(hx, hy + 1, 0, 1, color=color))
else:
fireballs.append(Fireball(hx - 1, hy, -1, 0, color=color))
fireballs.append(Fireball(hx + 1, hy, 1, 0, color=color))
return
# Остальные стреляющие змеи (stripe_green, stripe_blue, white_red, rainbow_snake)
hx, hy = head_cells[0]
color = PURPLE_FIRE if self.snake_type in ('white_red', 'rainbow_snake') else (255, 69, 0)
if self.direction in ('left', 'right'):
fireballs.append(Fireball(hx, hy - 1, 0, -1, color=color))
fireballs.append(Fireball(hx, hy + 1, 0, 1, color=color))
else:
fireballs.append(Fireball(hx - 1, hy, -1, 0, color=color))
fireballs.append(Fireball(hx + 1, hy, 1, 0, color=color))
def shoot_line(self, fireballs):
if self.width == 1:
head_x, head_y = self.body[0]
else:
head_x = self.body[0][0][0]
head_y = self.body[0][0][1]
color = PURPLE_FIRE
if self.direction in ('left', 'right'):
for dy in (-1, 0, 1):
fireballs.append(Fireball(head_x, head_y + dy, 0, -1, speed=2, color=color))
fireballs.append(Fireball(head_x, head_y + dy, 0, 1, speed=2, color=color))
else:
for dx in (-1, 0, 1):
fireballs.append(Fireball(head_x + dx, head_y, -1, 0, speed=2, color=color))
fireballs.append(Fireball(head_x + dx, head_y, 1, 0, speed=2, color=color))
def create_invisible_gas(self, gas_clouds):
# для бело-серой змеи: создаём невидимый газ радиусом 2-3 вокруг головы
if self.width == 1:
head_x, head_y = self.body[0]
else:
head_x = self.body[0][0][0]
head_y = self.body[0][0][1]
radius = random.randint(2, 3)
gas_clouds.append(SlimeGasCloud(head_x, head_y, radius, 3, lethal=False, invisible=True))
def move(self, bird_x=None, bird_y=None, fireballs=None, gas_clouds=None):
if not self.body:
return
# Стрельба для красной и чёрной змей
if self.snake_type in ('red') and fireballs is not None:
if self.shoot_cooldown <= 0:
self.shoot(fireballs)
self.shoot_cooldown = self.shoot_delay
else:
self.shoot_cooldown -= 1
# Стрельба для остальных стреляющих змей
if self.snake_type in ['stripe_green', 'stripe_blue'] and fireballs is not None:
if self.shoot_cooldown <= 0:
self.shoot(fireballs)
self.shoot_cooldown = self.shoot_delay
else:
self.shoot_cooldown -= 1
elif self.snake_type == 'white_red' and fireballs is not None:
if self.shoot_cooldown <= 0:
self.shoot_line(fireballs)
self.shoot_cooldown = self.shoot_delay
else:
self.shoot_cooldown -= 1
# Газ для бело-серой змеи
if self.snake_type == 'white_gray' and gas_clouds is not None:
if self.gas_cooldown <= 0:
self.create_invisible_gas(gas_clouds)
self.gas_cooldown = 1 # каждый ход
else:
self.gas_cooldown -= 1
if self.snake_type == 'rainbow_snake':
if self.rush_cooldown <= 0:
for _ in range(self.rush_distance):
self._move_one_step()
self.rush_cooldown = self.rush_delay
else:
self.rush_cooldown -= 1
steps = self.move_speed
for _ in range(steps):
self._move_one_step()
if self.turns_left > 0 and bird_x is not None:
if random.random() < 0.3:
new_direction = self.calculate_direction_to_player(bird_x, bird_y)
opposite = {'up': 'down', 'down': 'up', 'left': 'right', 'right': 'left'}
if new_direction != opposite.get(self.direction, ''):
self.direction = new_direction
self.turns_left -= 1
return
if (self.snake_type in ['dark_green', 'dark_blue', 'white',
'white_purple', 'white_gray']) and self.turns_left > 0 and bird_x is not None:
if random.random() < 0.4:
new_direction = self.calculate_direction_to_player(bird_x, bird_y)
opposite = {'up': 'down', 'down': 'up', 'left': 'right', 'right': 'left'}
if new_direction != opposite.get(self.direction, ''):
self.direction = new_direction
self.turns_left -= 1
if self.snake_type in ['swamp_green', 'pink_azure']:
if self.charge == 0:
self.charge = 1
self.charge_timer = 0
return
elif self.charge == 1:
self.charge_timer += 1
if self.charge_timer >= 2:
self.charge = 2
return
elif self.charge == 2:
steps = self.rush_distance
for _ in range(steps):
self._move_one_step()
self.charge = 0
return
steps = self.move_speed
for _ in range(steps):
self._move_one_step()
def _move_one_step(self):
if self.width == 1:
head_x, head_y = self.body[0]
if self.direction == 'up':
new_head = (head_x, head_y - 1)
elif self.direction == 'down':
new_head = (head_x, head_y + 1)
elif self.direction == 'left':
new_head = (head_x - 1, head_y)
else:
new_head = (head_x + 1, head_y)
self.body.appendleft(new_head)
self.body.pop()
else:
head = self.body[0]
new_head = []
for seg in head:
x, y = seg
if self.direction == 'up':
new_head.append((x, y - 1))
elif self.direction == 'down':
new_head.append((x, y + 1))
elif self.direction == 'left':
new_head.append((x - 1, y))
else:
new_head.append((x + 1, y))
self.body.appendleft(tuple(new_head))
self.body.pop()
def draw(self, screen):
self.rainbow_offset = (self.rainbow_offset + 1) % 360
for i, segment in enumerate(self.body):
if self.width == 1:
x, y = segment
if 0 <= x < GRID_SIZE and 0 <= y < GRID_SIZE:
rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
self._draw_segment(screen, rect, i, x, y)
else:
is_head = (i == 0)
for j, (x, y) in enumerate(segment):
if 0 <= x < GRID_SIZE and 0 <= y < GRID_SIZE:
rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
self._draw_segment(screen, rect, i, x, y, is_head, j)
def _draw_segment(self, screen, rect, i, x, y, is_head=False, part=0):
if self.snake_type == 'rainbow_snake':
hue = (self.rainbow_offset + i * 10) % 360
color = pygame.Color(0)
color.hsva = (hue, 100, 100)
head_color = body_color = color
else:
if self.snake_type == 'green':
head_color, body_color = DARK_GREEN, GREEN
elif self.snake_type == 'dark_green':
head_color, body_color = DARK_GREEN_PURPLE, DARK_GREEN_PURPLE
elif self.snake_type == 'bright_green':
head_color, body_color = BRIGHT_GREEN, BRIGHT_GREEN
elif self.snake_type == 'blue':
head_color, body_color = DARK_BLUE, BLUE
elif self.snake_type == 'dark_blue':
head_color, body_color = DARK_BLUE_PURPLE, DARK_BLUE_PURPLE
elif self.snake_type == 'light_blue':
head_color, body_color = LIGHT_BLUE, LIGHT_BLUE
elif self.snake_type == 'red':
head_color, body_color = DARK_RED_SNAKE, RED_SNAKE
elif self.snake_type == 'black':
head_color, body_color = BLACK_SNAKE, DARK_GRAY_BODY
elif self.snake_type == 'stripe_green':
head_color, body_color = DARK_GREEN, GREEN
elif self.snake_type == 'stripe_blue':
head_color, body_color = DARK_BLUE, BLUE
elif self.snake_type == 'swamp_green':
head_color, body_color = SWAMP_GREEN, (60, 80, 30)
elif self.snake_type == 'pink_azure':
head_color, body_color = PINK_AZURE, (200, 80, 140)
elif self.snake_type == 'white':
head_color, body_color = (200, 200, 200), WHITE_SNAKE
elif self.snake_type == 'white_purple':
head_color, body_color = (180, 130, 230), WHITE_SNAKE
elif self.snake_type == 'white_red':
head_color, body_color = (255, 150, 150), WHITE_SNAKE
elif self.snake_type == 'white_gray':
head_color, body_color = (160, 160, 160), (200, 200, 200)
else:
head_color, body_color = BLACK_SNAKE, DARK_GRAY_BODY
if i == 0 and (self.width == 1 or is_head):
pygame.draw.rect(screen, head_color, rect)
eye_size = CELL_SIZE // 8
# Глаза для ширины 2 и 3: симметричные
if self.width == 1:
eye_offset = CELL_SIZE // 4
pygame.draw.circle(screen, WHITE, (x * CELL_SIZE + eye_offset, y * CELL_SIZE + eye_offset), eye_size)
pygame.draw.circle(screen, WHITE, (x * CELL_SIZE + CELL_SIZE - eye_offset, y * CELL_SIZE + eye_offset),
eye_size)
pygame.draw.circle(screen, BLACK, (x * CELL_SIZE + eye_offset, y * CELL_SIZE + eye_offset),
eye_size // 2)
pygame.draw.circle(screen, BLACK, (x * CELL_SIZE + CELL_SIZE - eye_offset, y * CELL_SIZE + eye_offset),
eye_size // 2)
elif self.width == 2:
if part == 0:
eye_offset = CELL_SIZE // 4
pygame.draw.circle(screen, WHITE, (x * CELL_SIZE + eye_offset, y * CELL_SIZE + eye_offset),
eye_size)
pygame.draw.circle(screen, BLACK, (x * CELL_SIZE + eye_offset, y * CELL_SIZE + eye_offset),
eye_size // 2)
else:
eye_offset = CELL_SIZE // 4
pygame.draw.circle(screen, WHITE,
(x * CELL_SIZE + CELL_SIZE - eye_offset, y * CELL_SIZE + eye_offset), eye_size)
pygame.draw.circle(screen, BLACK,
(x * CELL_SIZE + CELL_SIZE - eye_offset, y * CELL_SIZE + eye_offset),
eye_size // 2)
else: # width == 3
if part == 0:
eye_offset = CELL_SIZE // 4
pygame.draw.circle(screen, RED_EYE, (x * CELL_SIZE + eye_offset, y * CELL_SIZE + eye_offset),
eye_size)
elif part == 2:
eye_offset = CELL_SIZE // 4
pygame.draw.circle(screen, RED_EYE,
(x * CELL_SIZE + CELL_SIZE - eye_offset, y * CELL_SIZE + eye_offset), eye_size)
else:
pygame.draw.rect(screen, body_color, rect)
if self.snake_type == 'dark_green':
for _ in range(8):
dot_x = x * CELL_SIZE + random.randint(5, CELL_SIZE - 5)
dot_y = y * CELL_SIZE + random.randint(5, CELL_SIZE - 5)
pygame.draw.circle(screen, PURPLE_SPOT, (dot_x, dot_y), random.randint(1, 2))
elif self.snake_type == 'dark_blue':
for _ in range(8):
dot_x = x * CELL_SIZE + random.randint(5, CELL_SIZE - 5)
dot_y = y * CELL_SIZE + random.randint(5, CELL_SIZE - 5)
pygame.draw.circle(screen, PURPLE_DOT, (dot_x, dot_y), random.randint(1, 2))
elif self.snake_type == 'bright_green':
pygame.draw.rect(screen, YELLOW_STRIPE,
(x * CELL_SIZE + 10, y * CELL_SIZE + 10, CELL_SIZE - 20, CELL_SIZE - 20), 2)
elif self.snake_type == 'light_blue':
pygame.draw.rect(screen, CYAN_STRIPE,
(x * CELL_SIZE + 10, y * CELL_SIZE + 10, CELL_SIZE - 20, CELL_SIZE - 20), 2)
elif self.snake_type == 'red':
pygame.draw.rect(screen, DARK_RED_SNAKE,
(x * CELL_SIZE + 15, y * CELL_SIZE + 15, CELL_SIZE - 30, CELL_SIZE - 30))
elif self.snake_type == 'stripe_green':
pygame.draw.rect(screen, ORANGE_STRIPE,
(x * CELL_SIZE + 5, y * CELL_SIZE + 5, CELL_SIZE - 10, CELL_SIZE - 10), 2)
elif self.snake_type == 'stripe_blue':
pygame.draw.rect(screen, BRIGHT_ORANGE_STRIPE,
(x * CELL_SIZE + 5, y * CELL_SIZE + 5, CELL_SIZE - 10, CELL_SIZE - 10), 2)
elif self.snake_type == 'white_purple':
pygame.draw.rect(screen, (150, 100, 200),
(x * CELL_SIZE + 5, y * CELL_SIZE + 5, CELL_SIZE - 10, CELL_SIZE - 10), 2)
elif self.snake_type == 'white_red':
for _ in range(5):
dot_x = x * CELL_SIZE + random.randint(5, CELL_SIZE - 5)
dot_y = y * CELL_SIZE + random.randint(5, CELL_SIZE - 5)
pygame.draw.circle(screen, RED_SNAKE, (dot_x, dot_y), random.randint(2, 4))
elif self.snake_type == 'rainbow_snake':
pygame.draw.rect(screen, WHITE, rect, 2)
elif self.snake_type == 'white_gray':
pygame.draw.rect(screen, (100, 100, 100), rect, 2)
pygame.draw.rect(screen, BLACK, rect, 2)
def get_all_positions(self):
positions = set()
if self.width == 1:
for x, y in self.body:
positions.add((x, y))
else:
for segment in self.body:
for x, y in segment:
positions.add((x, y))
return positions
def is_completely_off_grid(self):
for x, y in self.get_all_positions():
if 0 <= x < GRID_SIZE and 0 <= y < GRID_SIZE:
return False
return True
def create_snake_at_edge(snake_type='green'):
if snake_type == 'green':
length = random.choice([4, 5, 6])
width = 1
elif snake_type == 'dark_green':
length = random.choice([4, 5, 6, 7, 8])
width = 1
elif snake_type == 'bright_green':
length = random.choice([3, 4, 5])
width = 1
elif snake_type == 'blue':
length = random.choice([5, 6, 7, 8, 9, 10])
width = 1
elif snake_type == 'dark_blue':
length = random.choice([4, 5, 6, 7, 8, 9])
width = 1
elif snake_type == 'light_blue':
length = random.choice([3, 4])
width = 1
elif snake_type == 'red':
length = random.choice([14, 15, 16])
width = 2
elif snake_type == 'black':
length = 30
width = 3
elif snake_type == 'stripe_green':
length = random.choice([2, 3])
width = 1
elif snake_type == 'stripe_blue':
length = random.choice([2, 3, 4])
width = 1
elif snake_type == 'swamp_green':
length = random.choice([6, 7])
width = 1
elif snake_type == 'pink_azure':
length = random.choice([7, 8])
width = 1
elif snake_type == 'white':
length = random.choice([7, 8, 9, 10, 11, 12])
width = 1
elif snake_type == 'white_purple':
length = random.choice([8, 9, 10, 11, 12, 13])
width = 1
elif snake_type == 'white_red':
length = random.choice([4, 5])
width = 1
elif snake_type == 'rainbow_snake':
length = 17
width = 2
elif snake_type == 'white_gray':
length = random.choice([5, 6, 7])
width = 1
else:
length = 4
width = 1
side = random.choice(['top', 'bottom', 'left', 'right'])
if side == 'top':
x = random.randint(0, GRID_SIZE - width)
y = 0
direction = 'down'
elif side == 'bottom':
x = random.randint(0, GRID_SIZE - width)
y = GRID_SIZE - 1
direction = 'up'
elif side == 'left':
x = 0
y = random.randint(0, GRID_SIZE - width)
direction = 'right'
else:
x = GRID_SIZE - 1
y = random.randint(0, GRID_SIZE - width)
direction = 'left'
return Snake(x, y, length, direction, snake_type, width)
def check_collision(bird_x, bird_y, snakes, fireballs, invisible_clouds=None):
bird_pos = (bird_x, bird_y)
for snake in snakes:
if bird_pos in snake.get_all_positions():
return True
for fb in fireballs:
if fb.active and fb.get_pos() == bird_pos:
return True
if invisible_clouds:
for ic in invisible_clouds:
# Только смертельные облака убивают, невидимые – нет
if ic.lethal and bird_pos in ic.get_affected_cells():
return True
return False
def draw_grid():
for row in range(GRID_SIZE):
for col in range(GRID_SIZE):
rect = pygame.Rect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE)
if (row + col) % 2 == 0:
pygame.draw.rect(screen, GRAY, rect)
else:
pygame.draw.rect(screen, LIGHT_GRAY, rect)
pygame.draw.rect(screen, BLACK, rect, 1)
def is_cell_visible(cx, cy, bird_x, bird_y, fog=False):
"""Если fog=True, клетка видна только в радиусе FOG_RADIUS от птички."""
if not fog:
return True
return abs(cx - bird_x) + abs(cy - bird_y) <= FOG_RADIUS
# ---------------------- РџРђРЈРљР ----------------------
SPIDER_INFO_ORDERED = [
"light_gray", "brown_gray", "dark_gray_red_cross", "dark_gray_green_circle",
"dark_red", "big_dark_gray", "big_dark_gray_green_circle", "black_spider", "white_purple_circle",
"rainbow_spider"
]
SPIDER_INFO = {
"light_gray": {"size": 1, "fall_time": 3, "sit_time": 5, "color": SPIDER_LIGHT_GRAY, "special": "Нет",
"spawn_condition": "Всегда"},
"brown_gray": {"size": 1, "fall_time": 3, "sit_time": 30, "color": SPIDER_BROWN_GRAY, "special": "Нет",
"spawn_condition": "Всегда"},
"dark_gray_red_cross": {"size": 1, "fall_time": 3, "sit_time": 10, "color": SPIDER_DARK_GRAY,
"special": "Стрельба шарами (при падении и каждые 2 хода)", "cross": True,
"spawn_condition": "Всегда"},
"dark_gray_green_circle": {"size": 1, "fall_time": 3, "sit_time": 5, "color": SPIDER_DARK_GRAY,
"special": "Облако газа на 4-й ход", "green_circle": True, "spawn_condition": "Всегда"},
"dark_red": {"size": 2, "fall_time": 5, "sit_time": 20, "color": SPIDER_DARK_RED,
"special": "Стена огня 2x1 каждые 3 хода", "spawn_condition": "Редко, после убийств"},
"big_dark_gray": {"size": 2, "fall_time": 5, "sit_time": 10, "color": SPIDER_BIG_DARK_GRAY, "special": "Нет",
"spawn_condition": "После 2 dark_red"},
"big_dark_gray_green_circle": {"size": 2, "fall_time": 5, "sit_time": 10, "color": SPIDER_BIG_DARK_GRAY,
"special": "Облако газа (радиус 6) на 8-й ход",
"spawn_condition": "После 4 dark_red"},
"black_spider": {"size": 3, "fall_time": 7, "sit_time": 15, "color": SPIDER_BLACK,
"special": "Стреляет линиями из двух шаров во все 8 направлений",
"spawn_condition": "После 5 dark_red"},
"white_purple_circle": {"size": 1, "fall_time": 3, "sit_time": 15, "color": SPIDER_WHITE_PURPLE,
"special": "Линия из 2 шаров (скор.2) каждые 2 хода", "spawn_condition": "Всегда"},
"rainbow_spider": {"size": 1, "fall_time": 3, "sit_time": 15, "color": (255, 0, 255),
"special": "Стрельба в 8 направлений разными снарядами",
"spawn_condition": "Очень редко, всегда", "rainbow": True}
}
SPIDER_CATEGORIES = {
"Обычные": ["light_gray", "brown_gray", "dark_gray_red_cross", "dark_gray_green_circle", "white_purple_circle"],
"Большие": ["dark_red", "big_dark_gray", "big_dark_gray_green_circle", "black_spider"],
"Радужные": ["rainbow_spider"]
}
TAB_NAMES_SPIDERS = ["Обычные", "Большие", "Радужные"]
class SpiderFireball:
def __init__(self, x, y, dx, dy, speed=1, length=1, width=1, color=(255, 69, 0)):
self.x = x
self.y = y
self.dx = dx
self.dy = dy
self.speed = speed
self.length = length
self.width = width
self.active = True
self.color = color
self.move_timer = 0 # <-- добавить
def move(self):
if self.speed < 1.0:
self.move_timer += 1
if self.move_timer >= int(1 / self.speed):
self.move_timer = 0
self._move_step()
else:
for _ in range(int(self.speed)):
self._move_step()
if not self.active:
break
def _move_step(self):
self.x += self.dx
self.y += self.dy
if not (0 <= self.x < GRID_SIZE and 0 <= self.y < GRID_SIZE):
self.active = False
# ... остальные методы без изменений
def draw(self, screen):
if not self.active:
return
if self.length == 1 and self.width == 1:
rect = pygame.Rect(self.x * CELL_SIZE, self.y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
center = rect.center
radius = CELL_SIZE // 3
draw_droplet(screen, self.color, center, radius, (self.dx, self.dy), border_color=BLACK, border_width=2)
# Удаляем или комментируем старую точку
# pygame.draw.circle(screen, WHITE, center, CELL_SIZE // 5)
else:
# ... (стены и линии остаются без изменений)
if self.dx != 0:
width_px = CELL_SIZE * self.width
height_px = CELL_SIZE * self.length
x = self.x * CELL_SIZE - (self.width // 2) * CELL_SIZE
y = self.y * CELL_SIZE
else:
width_px = CELL_SIZE * self.length
height_px = CELL_SIZE * self.width
x = self.x * CELL_SIZE
y = self.y * CELL_SIZE - (self.width // 2) * CELL_SIZE
rect = pygame.Rect(x, y, width_px, height_px)
pygame.draw.rect(screen, self.color, rect)
pygame.draw.rect(screen, BLACK, rect, 3)
def get_positions(self):
pos = set()
if self.length == 1 and self.width == 1:
pos.add((self.x, self.y))
else:
if self.dx != 0:
for w in range(self.width):
for l in range(self.length):
pos.add((self.x + l * self.dx, self.y + w - self.width // 2))
else:
for w in range(self.width):
for l in range(self.length):
pos.add((self.x + w - self.width // 2, self.y + l * self.dy))
return pos
class GasCloud:
def __init__(self, x, y, radius, duration):
self.x = x
self.y = y
self.radius = radius
self.duration = duration
self.active = True
def update(self):
self.duration -= 1
if self.duration <= 0:
self.active = False
def draw(self, screen):
if not self.active:
return
cells = self.get_affected_cells()
for cx, cy in cells:
rect = pygame.Rect(cx * CELL_SIZE, cy * CELL_SIZE, CELL_SIZE, CELL_SIZE)
alpha = 100 + (self.duration * 20)
alpha = min(180, max(80, alpha))
cloud_surf = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
cloud_surf.fill((100, 200, 100, alpha))
pygame.draw.ellipse(cloud_surf, (120, 220, 120, alpha), (5, 5, CELL_SIZE - 10, CELL_SIZE - 10))
screen.blit(cloud_surf, rect)
def get_affected_cells(self):
cells = set()
for dx in range(-self.radius, self.radius + 1):
for dy in range(-self.radius, self.radius + 1):
if abs(dx) + abs(dy) <= self.radius:
nx, ny = self.x + dx, self.y + dy
if 0 <= nx < GRID_SIZE and 0 <= ny < GRID_SIZE:
cells.add((nx, ny))
return cells
class Shadow:
def __init__(self, x, y, size, fall_time, spider_type):
self.x = x
self.y = y
self.size = size
self.remaining = fall_time
self.spider_type = spider_type
def update(self):
self.remaining -= 1
return self.remaining <= 0
def draw(self, screen):
alpha = min(150, 50 + (self.remaining * 20))
surf = pygame.Surface((CELL_SIZE * self.size, CELL_SIZE * self.size), pygame.SRCALPHA)
surf.fill((0, 0, 0, alpha))
pygame.draw.rect(surf, (200, 200, 200, alpha), (0, 0, CELL_SIZE * self.size, CELL_SIZE * self.size), 2)
screen.blit(surf, (self.x * CELL_SIZE, self.y * CELL_SIZE))
class Spider:
def __init__(self, x, y, spider_type):
self.x = x
self.y = y
self.spider_type = spider_type
info = SPIDER_INFO[spider_type]
self.size = info["size"]
self.sit_time = info["sit_time"]
self.timer = 0
self.active = True
self.shoot_cooldown = 0
self.gas_triggered = False
self.gas_radius = 3 if spider_type == "dark_gray_green_circle" else 6 if spider_type == "big_dark_gray_green_circle" else 0
self.gas_delay = 4 if spider_type == "dark_gray_green_circle" else 8 if spider_type == "big_dark_gray_green_circle" else None
self.rainbow_offset = 0
def update(self, fireballs, gas_clouds, bird_x, bird_y):
self.timer += 1
self.rainbow_offset = (self.rainbow_offset + 5) % 360
if self.spider_type == "dark_gray_red_cross":
if self.timer % 2 == 0:
direction = random.choice([(1, 0), (-1, 0), (0, 1), (0, -1)])
dx, dy = direction
cx, cy = self.x + self.size // 2, self.y + self.size // 2
fireballs.append(SpiderFireball(cx + dx, cy + dy, dx, dy, speed=1))
elif self.spider_type == "dark_red":
if self.timer % 3 == 0 and self.timer > 0:
self.shoot_wall(fireballs, length=2, speed=1, color=(255, 69, 0))
elif self.spider_type == "black_spider":
# Новая стрельба: линии из двух шаров во все 8 направлений
if self.timer % 3 == 0 and self.timer > 0:
self.shoot_eight_directions(fireballs, speed=2, color=PURPLE_FIRE)
elif self.spider_type == "white_purple_circle":
if self.timer % 2 == 0:
self.shoot_line_of_two_balls(fireballs, speed=2, color=PURPLE_FIRE)
elif self.spider_type == "rainbow_spider":
if self.timer % 2 == 0:
self.shoot_rainbow(fireballs)
if self.gas_delay is not None and not self.gas_triggered and self.timer >= self.gas_delay:
self.gas_triggered = True
gas_clouds.append(GasCloud(self.x + self.size // 2, self.y + self.size // 2, self.gas_radius,
duration=2 if self.size == 1 else 3))
gas_clouds.append(GasCloud(self.x + self.size // 2 - 1, self.y + self.size // 2, self.gas_radius,
duration=0 if self.size == 1 else 3))
gas_clouds.append(GasCloud(self.x + self.size // 2, self.y + self.size // 2 - 1, self.gas_radius,
duration=0 if self.size == 1 else 3))
gas_clouds.append(GasCloud(self.x + self.size // 2 - 1, self.y + self.size // 2 - 1, self.gas_radius,
duration=0 if self.size == 1 else 3))
if self.timer >= self.sit_time:
self.active = False
def shoot_wall(self, fireballs, length, speed, color):
direction = random.choice([(1, 0), (-1, 0), (0, 1), (0, -1)])
dx, dy = direction
if dx != 0:
for i in range(self.size):
start_x = self.x + self.size // 2
start_y = self.y + i
fireballs.append(SpiderFireball(start_x, start_y, dx, dy, speed=speed, length=1, color=color))
else:
for i in range(self.size):
start_x = self.x + i
start_y = self.y + self.size // 2
fireballs.append(SpiderFireball(start_x, start_y, dx, dy, speed=speed, length=1, color=color))
def shoot_eight_directions(self, fireballs, speed, color):
# все 8 направлений
directions = [(1,0), (-1,0), (0,1), (0,-1), (1,1), (1,-1), (-1,1), (-1,-1)]
cx = self.x + self.size // 2
cy = self.y + self.size // 2
for dx, dy in directions:
# первый шар
fireballs.append(SpiderFireball(cx + dx, cy + dy, dx, dy, speed=speed, length=1, color=color))
# второй шар на следующей клетке
fireballs.append(SpiderFireball(cx + 2*dx, cy + 2*dy, dx, dy, speed=speed, length=1, color=color))
def shoot_grid_from_center(self, fireballs, width, length, speed, color):
direction = random.choice([(1, 0), (-1, 0), (0, 1), (0, -1)])
dx, dy = direction
center_x = self.x + self.size / 2.0
center_y = self.y + self.size / 2.0
if width % 2 == 0:
perp_offsets = [i - width / 2.0 + 0.5 for i in range(width)]
else:
perp_offsets = [i - width // 2 for i in range(width)]
if length % 2 == 0:
along_offsets = [i - length / 2.0 + 0.5 for i in range(length)]
else:
along_offsets = [i - length // 2 for i in range(length)]
for w in range(width):
for l in range(length):
if dx != 0:
start_x = center_x + along_offsets[l] * dx
start_y = center_y + perp_offsets[w]
else:
start_x = center_x + perp_offsets[w]
start_y = center_y + along_offsets[l] * dy
ball_x = int(round(start_x))
ball_y = int(round(start_y))
if 0 <= ball_x < GRID_SIZE and 0 <= ball_y < GRID_SIZE:
fireballs.append(SpiderFireball(ball_x, ball_y, dx, dy, speed=speed, length=1, color=color))
def shoot_line_of_two_balls(self, fireballs, speed, color):
direction = random.choice([(1, 0), (-1, 0), (0, 1), (0, -1)])
dx, dy = direction
cx = self.x + self.size // 2
cy = self.y + self.size // 2
fireballs.append(SpiderFireball(cx, cy, dx, dy, speed=speed, length=1, color=color))
fireballs.append(SpiderFireball(cx + dx, cy + dy, dx, dy, speed=speed, length=1, color=color))
def shoot_rainbow(self, fireballs):
directions = [(1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)]
types = [
{"speed": 1, "color": (255, 0, 0)},
{"speed": 2, "color": PURPLE_FIRE},
{"speed": 0.5, "color": (0, 255, 0)}
]
cx = self.x + self.size // 2
cy = self.y + self.size // 2
for dx, dy in directions:
t = random.choice(types)
fireballs.append(SpiderFireball(cx, cy, dx, dy, speed=t["speed"], length=1, color=t["color"]))
def draw(self, screen):
size_px = CELL_SIZE * self.size
x_px = self.x * CELL_SIZE
y_px = self.y * CELL_SIZE
if self.spider_type == "rainbow_spider":
hue = self.rainbow_offset % 360
color = pygame.Color(0)
color.hsva = (hue, 100, 100)
else:
color = SPIDER_INFO[self.spider_type]["color"]
for i in range(self.size):
for j in range(self.size):
sub_rect = pygame.Rect(x_px + i * CELL_SIZE, y_px + j * CELL_SIZE, CELL_SIZE, CELL_SIZE)
if self.spider_type == "rainbow_spider":
hue2 = (hue + (i + j) * 20) % 360
c2 = pygame.Color(0)
c2.hsva = (hue2, 100, 100)
pygame.draw.rect(screen, c2, sub_rect)
else:
pygame.draw.rect(screen, color, sub_rect)
pygame.draw.rect(screen, BLACK, sub_rect, 1)
outline_color = (255, 255, 200) if self.spider_type != "black_spider" else (150, 150, 150)
pygame.draw.rect(screen, outline_color, (x_px, y_px, size_px, size_px), 2)
if self.size == 1:
cx = x_px + CELL_SIZE // 2
cy = y_px + CELL_SIZE // 2
leg_color = (0, 0, 0)
offsets = [(-1, -1), (1, -1), (-1, 1), (1, 1), (-2, 0), (2, 0), (0, -2), (0, 2)]
for ox, oy in offsets:
ex = cx + ox * CELL_SIZE // 3
ey = cy + oy * CELL_SIZE // 3
pygame.draw.line(screen, leg_color, (cx, cy), (ex, ey), max(2, CELL_SIZE // 10))
eye_radius = max(2, CELL_SIZE // 8)
pygame.draw.circle(screen, WHITE, (cx - CELL_SIZE // 5, cy - CELL_SIZE // 5), eye_radius)
pygame.draw.circle(screen, WHITE, (cx + CELL_SIZE // 5, cy - CELL_SIZE // 5), eye_radius)
pygame.draw.circle(screen, BLACK, (cx - CELL_SIZE // 5, cy - CELL_SIZE // 5), eye_radius // 2)
pygame.draw.circle(screen, BLACK, (cx + CELL_SIZE // 5, cy - CELL_SIZE // 5), eye_radius // 2)
pygame.draw.line(screen, (0, 0, 0), (cx - 5, cy + 2), (cx - 10, cy + 8), 2)
pygame.draw.line(screen, (0, 0, 0), (cx + 5, cy + 2), (cx + 10, cy + 8), 2)
elif self.size == 2:
for i in range(2):
for j in range(2):
sub_rect = pygame.Rect(x_px + i * CELL_SIZE, y_px + j * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.rect(screen, color, sub_rect)
pygame.draw.rect(screen, outline_color, sub_rect, 1)
eye_radius = CELL_SIZE // 5
left_eye_center = (x_px + CELL_SIZE // 2, y_px + CELL_SIZE // 2)
right_eye_center = (x_px + 3 * CELL_SIZE // 2, y_px + CELL_SIZE // 2)
pygame.draw.circle(screen, WHITE, left_eye_center, eye_radius)
pygame.draw.circle(screen, WHITE, right_eye_center, eye_radius)
pygame.draw.circle(screen, BLACK, left_eye_center, eye_radius // 2)
pygame.draw.circle(screen, BLACK, right_eye_center, eye_radius // 2)
else:
for i in range(3):
for j in range(3):
sub_rect = pygame.Rect(x_px + i * CELL_SIZE, y_px + j * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.rect(screen, color, sub_rect)
pygame.draw.rect(screen, outline_color, sub_rect, 1)
eye_radius = CELL_SIZE // 4
eye1 = (x_px + CELL_SIZE // 2, y_px + CELL_SIZE // 2)
eye2 = (x_px + 5 * CELL_SIZE // 2, y_px + CELL_SIZE // 2)
pygame.draw.circle(screen, RED_EYE, eye1, eye_radius)
pygame.draw.circle(screen, RED_EYE, eye2, eye_radius)
pygame.draw.circle(screen, BLACK, eye1, eye_radius // 2)
pygame.draw.circle(screen, BLACK, eye2, eye_radius // 2)
if self.spider_type == "dark_gray_red_cross":
cx = x_px + size_px // 2
cy = y_px + size_px // 2
pygame.draw.line(screen, SPIDER_RED_CROSS, (cx - size_px // 3, cy), (cx + size_px // 3, cy),
max(2, size_px // 10))
pygame.draw.line(screen, SPIDER_RED_CROSS, (cx, cy - size_px // 3), (cx, cy + size_px // 3),
max(2, size_px // 10))
elif self.spider_type in ("dark_gray_green_circle", "big_dark_gray_green_circle"):
cx = x_px + size_px // 2
cy = y_px + size_px // 2
pygame.draw.circle(screen, SPIDER_GREEN_CIRCLE, (cx, cy), size_px // 3)
pygame.draw.circle(screen, BLACK, (cx, cy), size_px // 3, 1)
elif self.spider_type == "white_purple_circle":
cx = x_px + size_px // 2
cy = y_px + size_px // 2
pygame.draw.circle(screen, (128, 0, 128), (cx, cy), size_px // 3)
pygame.draw.circle(screen, BLACK, (cx, cy), size_px // 3, 1)
def get_positions(self):
pos = set()
for i in range(self.size):
for j in range(self.size):
pos.add((self.x + i, self.y + j))
return pos
def check_collision_spider(bird_x, bird_y, spiders, fireballs, gas_clouds):
bird_pos = (bird_x, bird_y)
for spider in spiders:
if bird_pos in spider.get_positions():
return True
for fb in fireballs:
if bird_pos in fb.get_positions():
return True
for gc in gas_clouds:
if bird_pos in gc.get_affected_cells():
return True
return False
# ---------------------- РЎР›РР—РќР ----------------------
SLIME_INFO_ORDERED = [
"green_slime_jump1", "green_slime_jump2", "dark_green_slime", "red_slime", "purple_blue_slime",
"black_slime", "dark_gray_slime", "light_gray_slime", "white_slime", "rainbow_slime",
"bright_orange_slime", "yellow_slime"
]
SLIME_INFO = {
"green_slime_jump1": {"jump_length": 2, "jump_freq": 1, "max_jumps": (4, 6), "color": (100, 255, 100, 180),
"death_effect": "balls", "death_balls_speed": 0.5,
"special": "После смерти выпускает 4 шара (скорость 0.5)"},
"green_slime_jump2": {"jump_length": 3, "jump_freq": 1, "max_jumps": (3, 5), "color": (120, 220, 120, 180),
"death_effect": "invis_gas", "gas_radius": 4, "gas_duration": 5,
"special": "После смерти облако невидимости (радиус 4, 5 ходов)"},
"dark_green_slime": {"jump_length": 4, "jump_freq": 2, "max_jumps": (2, 4), "color": (0, 150, 0, 180),
"death_effect": "gas", "gas_radius": 3, "gas_duration": 2,
"special": "После смерти ядовитое облако (радиус 3, 2 хода)"},
"red_slime": {"jump_length": 2, "jump_freq": 1, "max_jumps": (5, 7), "color": (255, 100, 100, 180),
"death_effect": "shoot", "shoot_speed": 1,
"special": "Каждый ход стреляет 4 шара (скорость 1), при смерти тоже стреляет"},
"purple_blue_slime": {"jump_length": 3, "jump_freq": 1, "max_jumps": (8, 9), "color": (100, 100, 255, 180),
"death_effect": "invis_gas", "gas_radius": 1, "gas_duration": 5,
"special": "Случайное направление прыжка, невидимый туман R1 после смерти"},
"black_slime": {"jump_length": 2, "jump_freq": 1, "max_jumps": (3, 5), "color": (30, 30, 30, 180),
"death_effect": "spawn_dark_gray", "special": "При смерти создаёт темно-серых слизней вокруг"},
"dark_gray_slime": {"jump_length": 2, "jump_freq": 1, "max_jumps": (2, 4), "color": (80, 80, 80, 180),
"death_effect": "spawn_light_gray", "special": "При смерти создаёт светло-серых слизней"},
"light_gray_slime": {"jump_length": 2, "jump_freq": 1, "max_jumps": (1, 3), "color": (150, 150, 150, 180),
"death_effect": "spawn_white", "special": "При смерти создаёт белых слизней", "harmless": True},
"white_slime": {"jump_length": 2, "jump_freq": 1, "max_jumps": (1, 2), "color": (230, 230, 230, 180),
"death_effect": "invis_gas", "gas_radius": 1, "gas_duration": 5,
"special": "После смерти невидимый газ (радиус 1, 5 ходов)", "harmless": True},
"rainbow_slime": {"jump_length": (2, 5), "jump_freq": 1, "max_jumps": None, "color": (255, 0, 255, 180),
"death_effect": None, "special": "Прыжки 2-5, стрельба 4 снарядами разных типов, облака",
"rainbow": True},
"bright_orange_slime": {"jump_length": 2, "jump_freq": 1, "max_jumps": (4, 6), "color": (255, 165, 0, 180),
"death_effect": "lava", "lava_radius": 3, "lava_duration": 10,
"special": "Прыгает через 2 клетки, оставляет лаву на клетке приземления. После смерти лава в радиусе 3 (10 ходов)"},
"yellow_slime": {"jump_length": 2, "jump_freq": 1, "max_jumps": (3, 7), "color": (255, 255, 0, 180),
"death_effect": "spawn_two", "special": "При смерти спавнит двух слизней (серо-зелёный или оранжевый)"}
}
SLIME_CATEGORIES = {
"Обычные": ["green_slime_jump1", "green_slime_jump2", "dark_green_slime", "red_slime", "purple_blue_slime"],
"Чёрные слизни": ["black_slime", "dark_gray_slime", "light_gray_slime", "white_slime"],
"РЇСЂРєРёРµ": ["bright_orange_slime", "yellow_slime"],
"Радужные": ["rainbow_slime"]
}
TAB_NAMES_SLIMES = ["Обычные", "Чёрные слизни", "Яркие", "Радужные"]
class SlimeBall:
def __init__(self, x, y, dx, dy, speed=1.0, color=(100, 255, 100, 200)):
self.x = x
self.y = y
self.dx = dx
self.dy = dy
self.speed = speed
self.move_timer = 0
self.active = True
self.color = color
def update(self):
if self.speed < 1.0:
self.move_timer += 1
if self.move_timer >= int(1 / self.speed):
self.move_timer = 0
self._move()
else:
for _ in range(int(self.speed)):
self._move()
if not self.active:
break
def _move(self):
self.x += self.dx
self.y += self.dy
if not (0 <= self.x < GRID_SIZE and 0 <= self.y < GRID_SIZE):
self.active = False
def draw(self, screen, invisible_clouds=None):
if not self.active:
return
if invisible_clouds:
for cloud in invisible_clouds:
if (self.x, self.y) in cloud.get_affected_cells():
return
rect = pygame.Rect(self.x * CELL_SIZE, self.y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
ball_surf = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
center = (CELL_SIZE // 2, CELL_SIZE // 2)
radius = CELL_SIZE // 3
# Рисуем каплю на временной поверхности
draw_droplet(ball_surf, self.color, center, radius, (self.dx, self.dy), border_color=BLACK, border_width=1)
screen.blit(ball_surf, rect)
def get_pos(self):
return (self.x, self.y)
class SlimeGasCloud:
def __init__(self, x, y, radius, duration, lethal=True, invisible=False):
self.x = x
self.y = y
self.radius = radius
self.duration = duration
self.active = True
self.lethal = lethal
self.invisible = invisible
def update(self):
self.duration -= 1
if self.duration <= 0:
self.active = False
def draw(self, screen):
if not self.active:
return
cells = self.get_affected_cells()
for cx, cy in cells:
rect = pygame.Rect(cx * CELL_SIZE, cy * CELL_SIZE, CELL_SIZE, CELL_SIZE)
if self.lethal:
alpha = 100 + (self.duration * 20)
alpha = min(180, max(80, alpha))
cloud_surf = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
cloud_surf.fill((100, 200, 100, alpha))
pygame.draw.ellipse(cloud_surf, (120, 220, 120, alpha), (5, 5, CELL_SIZE - 10, CELL_SIZE - 10))
screen.blit(cloud_surf, rect)
elif self.invisible:
alpha = 80 + (self.duration * 10)
alpha = min(150, max(50, alpha))
cloud_surf = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
cloud_surf.fill((150, 150, 150, alpha))
pygame.draw.ellipse(cloud_surf, (180, 180, 180, alpha), (5, 5, CELL_SIZE - 10, CELL_SIZE - 10))
screen.blit(cloud_surf, rect)
def get_affected_cells(self):
cells = set()
for dx in range(-self.radius, self.radius + 1):
for dy in range(-self.radius, self.radius + 1):
if abs(dx) + abs(dy) <= self.radius:
nx, ny = self.x + dx, self.y + dy
if 0 <= nx < GRID_SIZE and 0 <= ny < GRID_SIZE:
cells.add((nx, ny))
return cells
class LavaCell:
def __init__(self, x, y, duration):
self.x = x
self.y = y
self.duration = duration
self.active = True
def update(self):
self.duration -= 1
if self.duration <= 0:
self.active = False
def draw(self, screen):
if not self.active:
return
rect = pygame.Rect(self.x * CELL_SIZE, self.y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.rect(screen, (255, 69, 0), rect)
for _ in range(3):
px = self.x * CELL_SIZE + random.randint(5, CELL_SIZE-5)
py = self.y * CELL_SIZE + random.randint(5, CELL_SIZE-5)
pygame.draw.circle(screen, (255, 140, 0), (px, py), 2)
def get_pos(self):
return (self.x, self.y)
class Slime:
def __init__(self, x, y, slime_type, direction=None, lava_cells_list=None):
self.x = x
self.y = y
self.slime_type = slime_type
self.lava_cells_ref = lava_cells_list
info = SLIME_INFO[slime_type]
self.jump_length = info["jump_length"]
self.jump_freq = info["jump_freq"]
if info["max_jumps"] is not None:
self.max_jumps = random.randint(*info["max_jumps"])
else:
self.max_jumps = float('inf')
self.jumps_done = 0
self.color = info["color"]
self.active = True
self.turn_counter = 0
if direction is not None:
self.direction = direction
else:
self.direction = self._initial_direction()
self.shoot_cooldown = 0
self.rainbow_offset = 0
def _initial_direction(self):
if self.x == 0:
return 'right'
elif self.x == GRID_SIZE - 1:
return 'left'
elif self.y == 0:
return 'down'
else:
return 'up'
def update(self, slime_balls, gas_clouds, invisible_clouds, new_slimes=None, lava_cells_list=None):
if new_slimes is None:
new_slimes = []
self.turn_counter += 1
self.rainbow_offset = (self.rainbow_offset + 5) % 360
if self.jump_freq == 1 or (self.jump_freq == 2 and self.turn_counter % 2 == 1):
old_x, old_y = self.x, self.y
self._jump()
if self.slime_type == "bright_orange_slime" and lava_cells_list is not None:
lava_cells_list.append(LavaCell(self.x, self.y, duration=10))
if self.slime_type == "rainbow_slime":
self._shoot_rainbow(slime_balls)
if random.random() < 0.3:
gas_clouds.append(SlimeGasCloud(self.x, self.y, 2, 3, lethal=True))
if random.random() < 0.3:
invisible_clouds.append(SlimeGasCloud(self.x, self.y, 3, 5, lethal=False, invisible=True))
if self.slime_type == "red_slime":
self._shoot(slime_balls)
if self.jumps_done >= self.max_jumps and self.slime_type != "rainbow_slime":
self.active = False
self._on_death(slime_balls, gas_clouds, invisible_clouds, new_slimes, lava_cells_list)
if self.slime_type == "rainbow_slime":
if not (0 <= self.x < GRID_SIZE and 0 <= self.y < GRID_SIZE):
self.active = False
def _jump(self):
if self.slime_type in ["purple_blue_slime", "rainbow_slime"]:
self.direction = random.choice(['up', 'down', 'left', 'right'])
if self.slime_type == "rainbow_slime":
jump_dist = random.randint(2, 5)
else:
jump_dist = self.jump_length
dx, dy = 0, 0
if self.direction == 'up':
dy = -jump_dist
elif self.direction == 'down':
dy = jump_dist
elif self.direction == 'left':
dx = -jump_dist
elif self.direction == 'right':
dx = jump_dist
new_x = self.x + dx
new_y = self.y + dy
if 0 <= new_x < GRID_SIZE and 0 <= new_y < GRID_SIZE:
self.x = new_x
self.y = new_y
else:
opposite = {'up': 'down', 'down': 'up', 'left': 'right', 'right': 'left'}
self.direction = opposite[self.direction]
new_dx, new_dy = 0, 0
if self.direction == 'up':
new_dy = -jump_dist
elif self.direction == 'down':
new_dy = jump_dist
elif self.direction == 'left':
new_dx = -jump_dist
elif self.direction == 'right':
new_dx = jump_dist
new_x2 = self.x + new_dx
new_y2 = self.y + new_dy
if 0 <= new_x2 < GRID_SIZE and 0 <= new_y2 < GRID_SIZE:
self.x = new_x2
self.y = new_y2
self.jumps_done += 1
def _shoot(self, slime_balls):
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
ball_color = (255, 69, 0) if self.slime_type == "red_slime" else (100, 255, 100, 200)
for dx, dy in directions:
slime_balls.append(
SlimeBall(self.x + dx, self.y + dy, dx, dy, speed=SLIME_INFO[self.slime_type].get("shoot_speed", 1),
color=ball_color))
def _shoot_rainbow(self, slime_balls):
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
types = [
{"speed": 1, "color": (255, 0, 0)},
{"speed": 2, "color": PURPLE_FIRE},
{"speed": 1, "color": (0, 255, 0)}
]
for dx, dy in directions:
t = random.choice(types)
slime_balls.append(SlimeBall(self.x + dx, self.y + dy, dx, dy, speed=t["speed"], color=t["color"]))
def _on_death(self, slime_balls, gas_clouds, invisible_clouds, new_slimes, lava_cells_list):
info = SLIME_INFO[self.slime_type]
effect = info.get("death_effect")
if effect == "balls":
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
for dx, dy in directions:
slime_balls.append(SlimeBall(self.x + dx, self.y + dy, dx, dy, speed=info["death_balls_speed"],
color=(100, 255, 100, 200)))
elif effect == "invis_gas":
invisible_clouds.append(
SlimeGasCloud(self.x, self.y, info["gas_radius"], info["gas_duration"], lethal=False, invisible=True))
elif effect == "gas":
gas_clouds.append(SlimeGasCloud(self.x, self.y, info["gas_radius"], info["gas_duration"], lethal=True))
elif effect == "shoot":
ball_color = (255, 69, 0) if self.slime_type == "red_slime" else (100, 255, 100, 200)
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
for dx, dy in directions:
slime_balls.append(
SlimeBall(self.x + dx, self.y + dy, dx, dy, speed=info.get("shoot_speed", 1), color=ball_color))
elif effect == "spawn_dark_gray":
self._spawn_slime_around("dark_gray_slime", new_slimes, lava_cells_list)
elif effect == "spawn_light_gray":
self._spawn_slime_around("light_gray_slime", new_slimes, lava_cells_list)
elif effect == "spawn_white":
self._spawn_slime_around("white_slime", new_slimes, lava_cells_list)
elif effect == "lava" and lava_cells_list is not None:
radius = info.get("lava_radius", 3)
duration = info.get("lava_duration", 10)
for dx in range(-radius, radius+1):
for dy in range(-radius, radius+1):
if abs(dx) + abs(dy) <= radius:
nx, ny = self.x + dx, self.y + dy
if 0 <= nx < GRID_SIZE and 0 <= ny < GRID_SIZE:
lava_cells_list.append(LavaCell(nx, ny, duration))
elif effect == "spawn_two":
# Спавнит двух слизней: серо-зелёный (dark_green_slime) или оранжевый (bright_orange_slime)
for _ in range(2):
new_type = random.choice(["dark_green_slime", "bright_orange_slime"])
# Рщем соседнюю пустую клетку
for _ in range(10):
dx = random.choice([-1, 0, 1])
dy = random.choice([-1, 0, 1])
if dx == 0 and dy == 0:
continue
nx, ny = self.x + dx, self.y + dy
if 0 <= nx < GRID_SIZE and 0 <= ny < GRID_SIZE:
new_slime = Slime(nx, ny, new_type, None, lava_cells_list)
new_slimes.append(new_slime)
break
def _spawn_slime_around(self, slime_type, new_slimes, lava_cells_list):
directions = [(-1, 0, 'left'), (1, 0, 'right'), (0, -1, 'up'), (0, 1, 'down')]
for dx, dy, dir_name in directions:
nx, ny = self.x + dx, self.y + dy
if 0 <= nx < GRID_SIZE and 0 <= ny < GRID_SIZE:
new_slime = Slime(nx, ny, slime_type, dir_name, lava_cells_list)
new_slimes.append(new_slime)
# В draw для Ant, DungBeetle, StagBeetle, Anthill:
def draw(self, screen, invisible_clouds=None):
if invisible_clouds:
for cloud in invisible_clouds:
if (self.x, self.y) in cloud.get_affected_cells():
return
# ... остальной код рисования
rect = pygame.Rect(self.x * CELL_SIZE, self.y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
slime_surf = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
if self.slime_type == "rainbow_slime":
hue = self.rainbow_offset % 360
color = pygame.Color(0)
color.hsva = (hue, 100, 100)
color.a = 180
else:
color = self.color
pygame.draw.ellipse(slime_surf, color, (5, 5, CELL_SIZE - 10, CELL_SIZE - 10))
pygame.draw.ellipse(slime_surf, (255, 255, 255, 100), (10, 10, CELL_SIZE - 20, CELL_SIZE - 20), 2)
eye_color = (0, 0, 0, 200)
pygame.draw.circle(slime_surf, eye_color, (CELL_SIZE // 3, CELL_SIZE // 3), CELL_SIZE // 10)
pygame.draw.circle(slime_surf, eye_color, (2 * CELL_SIZE // 3, CELL_SIZE // 3), CELL_SIZE // 10)
screen.blit(slime_surf, rect)
def get_pos(self):
return (self.x, self.y)
def spawn_slime(slime_type, direction=None, lava_cells_list=None):
side = random.choice(['top', 'bottom', 'left', 'right'])
if side == 'top':
x = random.randint(0, GRID_SIZE - 1)
y = 0
elif side == 'bottom':
x = random.randint(0, GRID_SIZE - 1)
y = GRID_SIZE - 1
elif side == 'left':
x = 0
y = random.randint(0, GRID_SIZE - 1)
else:
x = GRID_SIZE - 1
y = random.randint(0, GRID_SIZE - 1)
return Slime(x, y, slime_type, direction, lava_cells_list)
def check_collision_slime(bird_x, bird_y, slimes, slime_balls, gas_clouds, invisible_clouds, lava_cells=None):
bird_pos = (bird_x, bird_y)
for slime in slimes:
# Светло-серые и белые слизни не убивают
if slime.slime_type in ("light_gray_slime", "white_slime"):
continue
if slime.get_pos() == bird_pos:
return True
for ball in slime_balls:
if ball.active and ball.get_pos() == bird_pos:
return True
for gc in gas_clouds:
if gc.lethal and bird_pos in gc.get_affected_cells():
return True
if lava_cells:
for lava in lava_cells:
if lava.active and lava.get_pos() == bird_pos:
return True
return False
# ---------------------- НАСЕКОМЫЕ ----------------------
INSECT_INFO_ORDERED = [
"ant", "dung_beetle", "stag_beetle", "anthill"
]
INSECT_INFO = {
"ant": {
"description": "Муравей. Появляется с краёв, движется со скоростью 1, поворачивает раз в 3 хода (вероятность 0.3).",
"size": 1, "speed": 1, "turn_cooldown": 3, "turn_chance": 0.3, "special": "Повороты",
"spawn_condition": "Всегда",
"color": ANT_COLOR
},
"dung_beetle": {
"description": "Навозник. 3 хода стоит, затем пробегает всю линию, оставляя яд (3 хода).",
"size": 1, "charge_time": 3, "special": "Линейный рывок, ядовитый след",
"spawn_condition": "Всегда",
"color": DUNG_BEETLE_COLOR
},
"stag_beetle": {
"description": "Рогач. 3 хода стоит, затем пробегает всю линию, оставляя невидимый газ (5 ходов) на пути и соседних клетках.",
"size": 1, "charge_time": 3, "special": "Линейный рывок, невидимый газ",
"spawn_condition": "Всегда",
"color": STAG_BEETLE_COLOR
},
"anthill": {
"description": "Муравейник. Размер 3x3, падает 5 ходов, стоит 21 ход, спавнит муравьёв раз в 8 ходов. При разрушении спавнит 4 муравья.",
"size": 3, "fall_time": 5, "sit_time": 21, "spawn_interval": 8, "spawn_ants_on_death": 4,
"special": "Спавн муравьёв",
"spawn_condition": "Всегда",
"color": ANTHILL_COLOR
}
}
INSECT_CATEGORIES = {
"Насекомые": ["ant", "dung_beetle", "stag_beetle", "anthill"]
}
TAB_NAMES_INSECTS = ["Насекомые"]
class PoisonCloud:
def __init__(self, x, y, duration):
self.x = x
self.y = y
self.duration = duration
self.active = True
def update(self):
self.duration -= 1
if self.duration <= 0:
self.active = False
def draw(self, screen):
if not self.active:
return
rect = pygame.Rect(self.x * CELL_SIZE, self.y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
alpha = 100 + (self.duration * 20)
alpha = min(180, max(80, alpha))
cloud_surf = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
cloud_surf.fill((100, 200, 100, alpha))
pygame.draw.ellipse(cloud_surf, (120, 220, 120, alpha), (5, 5, CELL_SIZE - 10, CELL_SIZE - 10))
screen.blit(cloud_surf, rect)
def get_pos(self):
return (self.x, self.y)
class InsectShadow:
def __init__(self, x, y, size, fall_time, insect_type):
self.x = x
self.y = y
self.size = size
self.remaining = fall_time
self.insect_type = insect_type
def update(self):
self.remaining -= 1
return self.remaining <= 0
def draw(self, screen):
alpha = min(150, 50 + (self.remaining * 20))
surf = pygame.Surface((CELL_SIZE * self.size, CELL_SIZE * self.size), pygame.SRCALPHA)
surf.fill((0, 0, 0, alpha))
pygame.draw.rect(surf, (200, 200, 200, alpha), (0, 0, CELL_SIZE * self.size, CELL_SIZE * self.size), 2)
screen.blit(surf, (self.x * CELL_SIZE, self.y * CELL_SIZE))
class Ant:
def __init__(self, x, y, direction=None):
self.x = x
self.y = y
self.direction = direction if direction else random.choice(['up', 'down', 'left', 'right'])
self.turn_cooldown = 0
self.active = True
def update(self, bird_x, bird_y, poison_clouds, invisible_clouds):
if self.turn_cooldown <= 0:
if random.random() < 0.3:
opposite = {'up': 'down', 'down': 'up', 'left': 'right', 'right': 'left'}
possible = [d for d in ['up', 'down', 'left', 'right'] if d != opposite.get(self.direction)]
self.direction = random.choice(possible)
self.turn_cooldown = 3
else:
self.turn_cooldown -= 1
dx, dy = 0, 0
if self.direction == 'up':
dy = -1
elif self.direction == 'down':
dy = 1
elif self.direction == 'left':
dx = -1
elif self.direction == 'right':
dx = 1
new_x = self.x + dx
new_y = self.y + dy
if 0 <= new_x < GRID_SIZE and 0 <= new_y < GRID_SIZE:
self.x = new_x
self.y = new_y
else:
self.direction = {'up': 'down', 'down': 'up', 'left': 'right', 'right': 'left'}[self.direction]
if not (0 <= self.x < GRID_SIZE and 0 <= self.y < GRID_SIZE):
self.active = False
def draw(self, screen):
rect = pygame.Rect(self.x * CELL_SIZE, self.y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
# Улучшенная отрисовка муравья
pygame.draw.ellipse(screen, ANT_COLOR, rect.inflate(-8, -8))
pygame.draw.rect(screen, BLACK, rect, 2)
# Глаза
eye_size = CELL_SIZE // 10
eye_offset = CELL_SIZE // 4
pygame.draw.circle(screen, WHITE, (self.x * CELL_SIZE + eye_offset, self.y * CELL_SIZE + eye_offset), eye_size)
pygame.draw.circle(screen, WHITE, (self.x * CELL_SIZE + CELL_SIZE - eye_offset, self.y * CELL_SIZE + eye_offset), eye_size)
pygame.draw.circle(screen, BLACK, (self.x * CELL_SIZE + eye_offset, self.y * CELL_SIZE + eye_offset), eye_size // 2)
pygame.draw.circle(screen, BLACK, (self.x * CELL_SIZE + CELL_SIZE - eye_offset, self.y * CELL_SIZE + eye_offset), eye_size // 2)
# РЈСЃРёРєРё
start = (self.x * CELL_SIZE + CELL_SIZE//2, self.y * CELL_SIZE + CELL_SIZE//4)
end1 = (self.x * CELL_SIZE + CELL_SIZE//3, self.y * CELL_SIZE)
end2 = (self.x * CELL_SIZE + 2*CELL_SIZE//3, self.y * CELL_SIZE)
pygame.draw.line(screen, BLACK, start, end1, 1)
pygame.draw.line(screen, BLACK, start, end2, 1)
def get_pos(self):
return (self.x, self.y)
class DungBeetle:
def __init__(self, x, y, direction):
self.x = x
self.y = y
self.direction = direction
self.state = 'charging'
self.charge_timer = 3
self.active = True
def update(self, bird_x, bird_y, poison_clouds):
if self.state == 'charging':
self.charge_timer -= 1
if self.charge_timer <= 0:
self.state = 'rushing'
elif self.state == 'rushing':
dx, dy = 0, 0
if self.direction == 'up':
dy = -1
elif self.direction == 'down':
dy = 1
elif self.direction == 'left':
dx = -1
elif self.direction == 'right':
dx = 1
while 0 <= self.x + dx < GRID_SIZE and 0 <= self.y + dy < GRID_SIZE:
self.x += dx
self.y += dy
poison_clouds.append(PoisonCloud(self.x, self.y, 3))
self.active = False
def draw(self, screen):
rect = pygame.Rect(self.x * CELL_SIZE, self.y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.ellipse(screen, DUNG_BEETLE_COLOR, rect.inflate(-8, -8))
pygame.draw.rect(screen, BLACK, rect, 2)
# Р РѕР¶РєРё
cx = self.x * CELL_SIZE + CELL_SIZE//2
cy = self.y * CELL_SIZE + CELL_SIZE//4
pygame.draw.line(screen, BLACK, (cx, cy), (cx-5, cy-5), 2)
pygame.draw.line(screen, BLACK, (cx, cy), (cx+5, cy-5), 2)
def get_pos(self):
return (self.x, self.y)
class StagBeetle:
def __init__(self, x, y, direction):
self.x = x
self.y = y
self.direction = direction
self.state = 'charging'
self.charge_timer = 3
self.active = True
def update(self, bird_x, bird_y, invisible_clouds):
if self.state == 'charging':
self.charge_timer -= 1
if self.charge_timer <= 0:
self.state = 'rushing'
elif self.state == 'rushing':
dx, dy = 0, 0
if self.direction == 'up':
dy = -1
elif self.direction == 'down':
dy = 1
elif self.direction == 'left':
dx = -1
elif self.direction == 'right':
dx = 1
while 0 <= self.x + dx < GRID_SIZE and 0 <= self.y + dy < GRID_SIZE:
self.x += dx
self.y += dy
for nx in range(self.x-1, self.x+2):
for ny in range(self.y-1, self.y+2):
if 0 <= nx < GRID_SIZE and 0 <= ny < GRID_SIZE:
invisible_clouds.append(SlimeGasCloud(nx, ny, 0, 5, lethal=False, invisible=True))
self.active = False
def draw(self, screen):
rect = pygame.Rect(self.x * CELL_SIZE, self.y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.ellipse(screen, STAG_BEETLE_COLOR, rect.inflate(-8, -8))
pygame.draw.rect(screen, BLACK, rect, 2)
# Большие рога
cx = self.x * CELL_SIZE + CELL_SIZE//2
cy = self.y * CELL_SIZE + CELL_SIZE//4
pygame.draw.line(screen, BLACK, (cx, cy), (cx-8, cy-8), 3)
pygame.draw.line(screen, BLACK, (cx, cy), (cx+8, cy-8), 3)
def get_pos(self):
return (self.x, self.y)
class Anthill:
def __init__(self, x, y):
self.x = x
self.y = y
self.size = 3
self.timer = 0
self.active = True
self.spawn_timer = 0
def update(self, ants_list):
self.timer += 1
if self.timer % 8 == 0: # реже спавним (было 4)
possible_positions = []
for dx in range(-1, self.size+1):
for dy in range(-1, self.size+1):
nx = self.x + dx
ny = self.y + dy
if (dx < 0 or dx >= self.size or dy < 0 or dy >= self.size):
if 0 <= nx < GRID_SIZE and 0 <= ny < GRID_SIZE:
possible_positions.append((nx, ny))
if possible_positions:
sx, sy = random.choice(possible_positions)
direction = random.choice(['up', 'down', 'left', 'right'])
ants_list.append(Ant(sx, sy, direction))
if self.timer >= 21:
self.active = False
for _ in range(4):
dx = random.choice([-1, 1])
dy = random.choice([-1, 1])
nx = self.x + self.size//2 + dx
ny = self.y + self.size//2 + dy
if 0 <= nx < GRID_SIZE and 0 <= ny < GRID_SIZE:
ants_list.append(Ant(nx, ny, random.choice(['up', 'down', 'left', 'right'])))
def draw(self, screen):
for i in range(self.size):
for j in range(self.size):
rect = pygame.Rect((self.x+i) * CELL_SIZE, (self.y+j) * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.rect(screen, ANTHILL_COLOR, rect)
pygame.draw.rect(screen, BLACK, rect, 1)
# Холмик
center_x = self.x + 1
center_y = self.y + 1
rect_center = pygame.Rect(center_x * CELL_SIZE, center_y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.ellipse(screen, (50, 30, 10), rect_center.inflate(-10, -10))
# Р’С…РѕРґ
pygame.draw.ellipse(screen, BLACK, rect_center.inflate(-20, -20), 2)
def get_positions(self):
pos = set()
for i in range(self.size):
for j in range(self.size):
pos.add((self.x + i, self.y + j))
return pos
def spawn_insect(insect_type):
side = random.choice(['top', 'bottom', 'left', 'right'])
if side == 'top':
x = random.randint(0, GRID_SIZE - 1)
y = 0
direction = 'down'
elif side == 'bottom':
x = random.randint(0, GRID_SIZE - 1)
y = GRID_SIZE - 1
direction = 'up'
elif side == 'left':
x = 0
y = random.randint(0, GRID_SIZE - 1)
direction = 'right'
else:
x = GRID_SIZE - 1
y = random.randint(0, GRID_SIZE - 1)
direction = 'left'
if insect_type == "ant":
return Ant(x, y, direction)
elif insect_type == "dung_beetle":
return DungBeetle(x, y, direction)
elif insect_type == "stag_beetle":
return StagBeetle(x, y, direction)
elif insect_type == "anthill":
for _ in range(50):
tx = random.randint(1, GRID_SIZE - 4)
ty = random.randint(1, GRID_SIZE - 4)
return Anthill(tx, ty)
return Anthill(2, 2)
def check_collision_insect(bird_x, bird_y, ants, beetles, anthills, poison_clouds, invisible_clouds):
bird_pos = (bird_x, bird_y)
for ant in ants:
if ant.active and ant.get_pos() == bird_pos:
return True
for beetle in beetles:
if beetle.active and beetle.get_pos() == bird_pos:
return True
for ah in anthills:
if ah.active and bird_pos in ah.get_positions():
return True
for pc in poison_clouds:
if pc.active and pc.get_pos() == bird_pos:
return True
for ic in invisible_clouds:
if ic.active and ic.lethal and bird_pos in ic.get_affected_cells():
return True
return False
# ---------------------- РЫБЫ ----------------------
FISH_INFO_ORDERED = [
"swallow", "swordfish", "shark", "octopus"
]
FISH_INFO = {
"swallow": {"size": 1, "color": (100, 200, 255), "description": "Рыба-ласточка. Каждый 3-й ход выпрыгивает на 2 клетки над водой."},
"swordfish": {"size": 2, "color": (80, 80, 220), "description": "Рыба-меч. Длина 2. После 3 ходов вылетает и летит до конца со скоростью 5."},
"shark": {"size": 2, "color": (128, 128, 128), "description": "Акула. После 3 ходов выпрыгивает и летит в сторону игрока со скоростью 3."},
"octopus": {"size": 3, "color": (255, 0, 0), "description": "Осьминог. Сидит 3 хода под водой, затем 10 ходов на поле. Каждый ход появляются 4 новых щупальца."}
}
class Fish:
def __init__(self, fish_type):
self.type = fish_type
self.info = FISH_INFO[fish_type]
self.size = self.info["size"]
self.color = self.info["color"]
self.underwater = True
self.active = True
self.timer = 0
self.direction = None
self.x = 0
self.y = 0
self.spawn_at_edge()
self.speed = 1
def spawn_at_edge(self):
side = random.choice(['top', 'bottom', 'left', 'right'])
if side == 'top':
self.x = random.randint(0, GRID_SIZE - self.size)
self.y = 0
self.direction = 'down'
elif side == 'bottom':
self.x = random.randint(0, GRID_SIZE - self.size)
self.y = GRID_SIZE - self.size
self.direction = 'up'
elif side == 'left':
self.x = 0
self.y = random.randint(0, GRID_SIZE - self.size)
self.direction = 'right'
else:
self.x = GRID_SIZE - self.size
self.y = random.randint(0, GRID_SIZE - self.size)
self.direction = 'left'
def get_positions(self):
pos = set()
for i in range(self.size):
for j in range(self.size):
pos.add((self.x + i, self.y + j))
return pos
def move(self, enemies=None):
if not self.active:
return
steps = self.speed
for _ in range(steps):
old_positions = self.get_positions()
dx = dy = 0
if self.direction == 'up':
dy = -1
elif self.direction == 'down':
dy = 1
elif self.direction == 'left':
dx = -1
elif self.direction == 'right':
dx = 1
self.x += dx
self.y += dy
if not self.underwater and enemies is not None:
self.kill_enemies_in_cell(enemies)
if self.is_off_grid():
self.active = False
break
def kill_enemies_in_cell(self, enemies):
for enemy in enemies[:]:
if enemy is self:
continue
enemy_positions = set()
if hasattr(enemy, 'get_all_positions'):
enemy_positions = enemy.get_all_positions()
elif hasattr(enemy, 'get_positions'):
enemy_positions = enemy.get_positions()
elif hasattr(enemy, 'get_pos'):
pos = enemy.get_pos()
if pos:
enemy_positions = {pos}
if enemy_positions & self.get_positions():
enemy.active = False
def is_off_grid(self):
for pos in self.get_positions():
x, y = pos
if 0 <= x < GRID_SIZE and 0 <= y < GRID_SIZE:
return False
return True
def update(self, bird_x, bird_y):
self.timer += 1
class SwallowFish(Fish):
def __init__(self):
super().__init__("swallow")
self.surface_timer = 0
self.surface_steps = 0
self.speed = 1
def update(self, bird_x, bird_y, enemies=None):
super().update(bird_x, bird_y)
if self.underwater:
if self.timer % 3 == 0:
self.underwater = False
self.surface_steps = 2
else:
self.move(enemies)
self.surface_steps -= 1
if self.surface_steps <= 0:
self.underwater = True
if self.underwater:
self.move()
if self.is_off_grid():
self.active = False
def draw(self, screen):
if not self.active:
return
for i in range(self.size):
for j in range(self.size):
x = (self.x + i) * CELL_SIZE
y = (self.y + j) * CELL_SIZE
rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
if self.underwater:
s = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
s.fill((0, 0, 0, 120))
screen.blit(s, rect)
else:
# Тело
body_rect = rect.inflate(-10, -6)
pygame.draw.ellipse(screen, self.color, body_rect)
# Глаз
cx = x + CELL_SIZE // 2
cy = y + CELL_SIZE // 2
# Определяем угол направления
angle_map = {'up': -math.pi/2, 'down': math.pi/2, 'left': math.pi, 'right': 0}
angle = angle_map.get(self.direction, 0)
eye_offset = (CELL_SIZE//3, -CELL_SIZE//6)
eye_x, eye_y = rotate_point(cx + eye_offset[0], cy + eye_offset[1], angle, cx, cy)
pygame.draw.circle(screen, WHITE, (int(eye_x), int(eye_y)), 4)
pygame.draw.circle(screen, BLACK, (int(eye_x), int(eye_y)), 2)
# Хвост (треугольник сзади)
tail_points = []
if self.direction == 'up':
tail_points = [(x+CELL_SIZE//2, y+CELL_SIZE-5), (x+CELL_SIZE//3, y+CELL_SIZE-15), (x+2*CELL_SIZE//3, y+CELL_SIZE-15)]
elif self.direction == 'down':
tail_points = [(x+CELL_SIZE//2, y+5), (x+CELL_SIZE//3, y+15), (x+2*CELL_SIZE//3, y+15)]
elif self.direction == 'left':
tail_points = [(x+CELL_SIZE-5, y+CELL_SIZE//2), (x+CELL_SIZE-15, y+CELL_SIZE//3), (x+CELL_SIZE-15, y+2*CELL_SIZE//3)]
else: # right
tail_points = [(x+5, y+CELL_SIZE//2), (x+15, y+CELL_SIZE//3), (x+15, y+2*CELL_SIZE//3)]
pygame.draw.polygon(screen, self.color, tail_points)
pygame.draw.polygon(screen, BLACK, tail_points, 1)
# Плавник сверху
fin_points = []
if self.direction in ('up', 'down'):
fin_points = [(x+CELL_SIZE//2, y+CELL_SIZE//2-8), (x+CELL_SIZE//2-10, y+CELL_SIZE//2-18), (x+CELL_SIZE//2+10, y+CELL_SIZE//2-18)]
else:
fin_points = [(x+CELL_SIZE//2-8, y+CELL_SIZE//2), (x+CELL_SIZE//2-18, y+CELL_SIZE//2-10), (x+CELL_SIZE//2-18, y+CELL_SIZE//2+10)]
pygame.draw.polygon(screen, (min(255, self.color[0]+30), min(255, self.color[1]+30), min(255, self.color[2]+30)), fin_points)
pygame.draw.polygon(screen, BLACK, fin_points, 1)
pygame.draw.rect(screen, BLACK, rect, 2)
class Swordfish(Fish):
def __init__(self):
super().__init__("swordfish")
self.length = 2
self.underwater = True
self.rush = False
self.speed = 1
def update(self, bird_x, bird_y, enemies=None):
super().update(bird_x, bird_y)
if self.underwater:
self.move()
if self.timer >= 3 and not self.rush:
self.rush = True
self.underwater = False
self.speed = 5
else:
self.move(enemies)
if self.is_off_grid():
self.active = False
def get_positions(self):
pos = set()
if self.direction in ('up', 'down'):
for i in range(self.length):
pos.add((self.x, self.y + i))
else:
for i in range(self.length):
pos.add((self.x + i, self.y))
return pos
def draw(self, screen):
if not self.active:
return
# Рисуем два сегмента тела (длина 2)
positions = list(self.get_positions())
if len(positions) < 2:
return
# Определяем голову (первый сегмент по направлению) и хвост
head_pos = positions[0] if self.direction in ('right','down') else positions[1]
tail_pos = positions[1] if self.direction in ('right','down') else positions[0]
# Рисуем тело
for pos in positions:
x, y = pos
rect = pygame.Rect(x*CELL_SIZE, y*CELL_SIZE, CELL_SIZE, CELL_SIZE)
if self.underwater:
s = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
s.fill((0,0,0,120))
screen.blit(s, rect)
else:
pygame.draw.ellipse(screen, self.color, rect.inflate(-6, -6))
pygame.draw.rect(screen, BLACK, rect, 2)
# Меч (только у головы)
head_x, head_y = head_pos
head_rect = pygame.Rect(head_x*CELL_SIZE, head_y*CELL_SIZE, CELL_SIZE, CELL_SIZE)
cx = head_rect.centerx
cy = head_rect.centery
sword_length = CELL_SIZE
if self.direction == 'right':
end_x, end_y = cx + sword_length, cy
elif self.direction == 'left':
end_x, end_y = cx - sword_length, cy
elif self.direction == 'down':
end_x, end_y = cx, cy + sword_length
else: # up
end_x, end_y = cx, cy - sword_length
pygame.draw.line(screen, (200,200,200), (cx, cy), (end_x, end_y), 4)
# Глаз
eye_offset = (CELL_SIZE//3, -CELL_SIZE//6)
angle_map = {'up': -math.pi/2, 'down': math.pi/2, 'left': math.pi, 'right': 0}
angle = angle_map.get(self.direction, 0)
ex, ey = rotate_point(cx + eye_offset[0], cy + eye_offset[1], angle, cx, cy)
pygame.draw.circle(screen, WHITE, (int(ex), int(ey)), 4)
pygame.draw.circle(screen, BLACK, (int(ex), int(ey)), 2)
# Плавники
if self.direction in ('up','down'):
fin1 = (cx-10, cy)
fin2 = (cx-20, cy-10)
fin3 = (cx-20, cy+10)
else:
fin1 = (cx, cy-10)
fin2 = (cx-10, cy-20)
fin3 = (cx+10, cy-20)
pygame.draw.polygon(screen, (100,100,200), [fin1, fin2, fin3])
pygame.draw.polygon(screen, BLACK, [fin1, fin2, fin3], 1)
class Shark(Fish):
def __init__(self):
super().__init__("shark")
self.size = 2
self.underwater = True
self.rush = False
self.speed = 1
def update(self, bird_x, bird_y, enemies=None):
super().update(bird_x, bird_y)
if self.underwater:
self.move()
if self.timer >= 3 and not self.rush:
self.rush = True
self.underwater = False
self.speed = 3
dx = bird_x - (self.x + self.size//2)
dy = bird_y - (self.y + self.size//2)
if abs(dx) > abs(dy):
self.direction = 'right' if dx > 0 else 'left'
else:
self.direction = 'down' if dy > 0 else 'up'
else:
self.move(enemies)
if self.is_off_grid():
self.active = False
def draw(self, screen):
if not self.active:
return
for i in range(self.size):
for j in range(self.size):
x = (self.x + i) * CELL_SIZE
y = (self.y + j) * CELL_SIZE
rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
if self.underwater:
s = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
s.fill((0,0,0,120))
screen.blit(s, rect)
else:
# Тело акулы (серое)
body_rect = rect.inflate(-8, -4)
pygame.draw.ellipse(screen, self.color, body_rect)
# Брюхо светлее
belly_rect = rect.inflate(-12, -8)
belly_rect.y += 4
pygame.draw.ellipse(screen, (200,200,200), belly_rect)
# Глаз
cx = x + CELL_SIZE // 2
cy = y + CELL_SIZE // 2
angle_map = {'up': -math.pi/2, 'down': math.pi/2, 'left': math.pi, 'right': 0}
angle = angle_map.get(self.direction, 0)
eye_offset = (CELL_SIZE//3, -CELL_SIZE//6)
ex, ey = rotate_point(cx + eye_offset[0], cy + eye_offset[1], angle, cx, cy)
pygame.draw.circle(screen, WHITE, (int(ex), int(ey)), 5)
pygame.draw.circle(screen, BLACK, (int(ex), int(ey)), 2)
# Жаберные щели
gill_offset = (-CELL_SIZE//4, 0)
gx, gy = rotate_point(cx + gill_offset[0], cy + gill_offset[1], angle, cx, cy)
for k in range(3):
line_start = (gx + k*3, gy - 5)
line_end = (gx + k*3, gy + 5)
lsx, lsy = rotate_point(line_start[0], line_start[1], angle, cx, cy)
lex, ley = rotate_point(line_end[0], line_end[1], angle, cx, cy)
pygame.draw.line(screen, (50,50,50), (lsx, lsy), (lex, ley), 2)
# Плавник на спине
fin_points = [(cx, cy-12), (cx-12, cy-25), (cx+12, cy-25)]
fin_rot = [rotate_point(px, py, angle, cx, cy) for (px,py) in fin_points]
pygame.draw.polygon(screen, (80,80,80), fin_rot)
pygame.draw.polygon(screen, BLACK, fin_rot, 1)
# РҐРІРѕСЃС‚
tail_len = 20
tail_base = (cx, cy+12) if self.direction in ('up','down') else (cx+12, cy)
tail_tip1 = (tail_base[0]-10, tail_base[1]+tail_len) if self.direction in ('up','down') else (tail_base[0]+tail_len, tail_base[1]-10)
tail_tip2 = (tail_base[0]+10, tail_base[1]+tail_len) if self.direction in ('up','down') else (tail_base[0]+tail_len, tail_base[1]+10)
t1 = rotate_point(tail_tip1[0], tail_tip1[1], angle, cx, cy)
t2 = rotate_point(tail_tip2[0], tail_tip2[1], angle, cx, cy)
base_rot = rotate_point(tail_base[0], tail_base[1], angle, cx, cy)
pygame.draw.polygon(screen, self.color, [base_rot, t1, t2])
pygame.draw.polygon(screen, BLACK, [base_rot, t1, t2], 1)
pygame.draw.rect(screen, BLACK, rect, 2)
class Octopus(Fish):
def __init__(self):
super().__init__("octopus")
self.size = 3
self.underwater = True
self.phase = "underwater"
self.tentacles = set()
self.speed = 0
# Увеличено время жизни на поверхности с 10 до 30 ходов
self.surface_duration = 30
def update(self, bird_x, bird_y, enemies=None):
self.timer += 1
if self.phase == "underwater":
if self.timer >= 3:
self.phase = "surface"
self.underwater = False
self.timer = 0
self.spawn_tentacles(bird_x, bird_y)
elif self.phase == "surface":
if self.timer >= self.surface_duration:
self.active = False
return
self.spawn_tentacles(bird_x, bird_y)
def spawn_tentacles(self, bird_x, bird_y):
forbidden = set()
forbidden.add((bird_x, bird_y))
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
nx, ny = bird_x + dx, bird_y + dy
if 0 <= nx < GRID_SIZE and 0 <= ny < GRID_SIZE:
forbidden.add((nx, ny))
for i in range(self.size):
for j in range(self.size):
forbidden.add((self.x + i, self.y + j))
for _ in range(4):
attempts = 0
while attempts < 100:
tx = random.randint(0, GRID_SIZE - 1)
ty = random.randint(0, GRID_SIZE - 1)
if (tx, ty) not in forbidden and (tx, ty) not in self.tentacles:
self.tentacles.add((tx, ty))
forbidden.add((tx, ty))
break
attempts += 1
def get_positions(self):
pos = set()
for i in range(self.size):
for j in range(self.size):
pos.add((self.x + i, self.y + j))
return pos
def draw(self, screen):
if not self.active:
return
# Тело 3x3 (улучшенное)
for i in range(self.size):
for j in range(self.size):
x = (self.x + i) * CELL_SIZE
y = (self.y + j) * CELL_SIZE
rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
if self.underwater:
s = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
s.fill((0,0,0,120))
screen.blit(s, rect)
else:
# Основное тело
body_rect = rect.inflate(-6, -6)
pygame.draw.ellipse(screen, self.color, body_rect)
# Глаза (два больших)
cx = x + CELL_SIZE//2
cy = y + CELL_SIZE//2
eye_offsets = [(-CELL_SIZE//3, -CELL_SIZE//4), (CELL_SIZE//3, -CELL_SIZE//4)]
for ox, oy in eye_offsets:
ex, ey = cx + ox, cy + oy
pygame.draw.circle(screen, WHITE, (int(ex), int(ey)), 6)
pygame.draw.circle(screen, BLACK, (int(ex), int(ey)), 3)
pygame.draw.circle(screen, (200,0,0), (int(ex+1), int(ey+1)), 1) # блик
# Р РѕС‚
mouth_y = cy + CELL_SIZE//4
pygame.draw.arc(screen, BLACK, (cx-8, mouth_y-4, 16, 8), 0, math.pi, 2)
pygame.draw.rect(screen, BLACK, rect, 2)
# Щупальца (улучшенные)
if not self.underwater:
for tx, ty in self.tentacles:
tx_rect = pygame.Rect(tx * CELL_SIZE, ty * CELL_SIZE, CELL_SIZE, CELL_SIZE)
# Рисуем извивающиеся щупальца
points = []
cx = tx_rect.centerx
cy = tx_rect.centery
for k in range(4):
offset_x = math.sin(pygame.time.get_ticks()/200 + k) * 8
offset_y = math.cos(pygame.time.get_ticks()/200 + k) * 8
points.append((cx + offset_x, cy + offset_y - 10 + k*6))
if len(points) >= 2:
pygame.draw.lines(screen, (200,100,200), False, points, 4)
# РџСЂРёСЃРѕСЃРєРё
pygame.draw.circle(screen, (150,50,150), (cx, cy-5), 5)
pygame.draw.rect(screen, BLACK, tx_rect, 2)
def check_tentacle_collision(self, bird_x, bird_y):
return (bird_x, bird_y) in self.tentacles
def spawn_fish(fish_type):
if fish_type == "swallow":
return SwallowFish()
elif fish_type == "swordfish":
return Swordfish()
elif fish_type == "shark":
return Shark()
elif fish_type == "octopus":
return Octopus()
else:
return None
def check_collision_fish(bird_x, bird_y, fishes):
bird_pos = (bird_x, bird_y)
for fish in fishes:
if not fish.underwater:
if bird_pos in fish.get_positions():
return True
if isinstance(fish, Octopus) and fish.check_tentacle_collision(bird_x, bird_y):
return True
return False
# ---------------------- СПОСОБНОСТР----------------------
ABILITY_INFO = [
{"name": "Гэмблинг", "key": 1, "slots": 1, "uses": 10, "cooldown": 1,
"description": "Телепортирует на случайную пустую клетку."},
{"name": "Освежитель", "key": 2, "slots": 1, "uses": 5, "cooldown": 3,
"description": "Удаляет ядовитый и невидимый газ в радиусе 3."},
{"name": "Айсаир", "key": 3, "slots": 2, "uses": 3, "cooldown": 3,
"description": "Ослабляет огненные шары в радиусе 3:"},
{"name": "Мощные крылья", "key": 4, "slots": 2, "uses": 5, "cooldown": 2,
"description": "Перемещает на 3 клетки в направленииц движения."},
{"name": "Ремнант", "key": 5, "slots": 3, "uses": 2, "cooldown": 10,
"description": "Неуязвимость на 4 хода."},
{"name": "The world", "key": 6, "slots": 3, "uses": 3, "cooldown": 7,
"description": "Семерной ход."},
{"name": "Метод скипа", "key": 7, "slots": 1, "uses": 20, "cooldown": 0,
"description": "Пропускает один ход."}
]
selected_abilities = {}
def show_ability_selection(mode):
global current_state, selected_abilities
print(f"[DEBUG] ability_selection called with mode = {mode}")
# Определяем максимальное количество слотов
if mode.startswith("level"):
level_num = int(mode[5:])
info = LEVEL_INFO[level_num]
max_slots = info["max_slots"]
elif mode in ["challenge1", "challenge2"]:
max_slots = 0
elif mode == "sandbox":
max_slots = float('inf')
else:
max_slots = 5 # для выживания и обычных режимов
selected = {i: False for i in range(len(ABILITY_INFO))}
def start_game_with_abilities():
nonlocal selected
global current_state, selected_abilities
chosen = {}
total_slots = 0
for idx, ab_info in enumerate(ABILITY_INFO):
if selected[idx]:
total_slots += ab_info["slots"]
if total_slots <= max_slots or max_slots == float('inf'):
for idx, ab_info in enumerate(ABILITY_INFO):
if selected[idx]:
chosen[ab_info["name"]] = {
"uses": ab_info["uses"],
"cooldown": 0,
"slots": ab_info["slots"],
"key": ab_info["key"],
"max_uses": ab_info["uses"],
"max_cooldown": ab_info["cooldown"]
}
selected_abilities = chosen
if mode == "snakes":
current_state = "game"
elif mode == "spiders":
current_state = "spider_game"
elif mode == "slimes":
current_state = "slime_game"
elif mode == "insects":
current_state = "insect_game"
elif mode == "fish":
current_state = "fish_game"
elif mode == "sandbox":
current_state = "sandbox_play"
elif mode.startswith("level"):
current_state = mode
else:
current_state = "menu"
print(f"[DEBUG] start_game: new current_state = {current_state}")
else:
print(f"[DEBUG] Not enough slots! Used {total_slots}, max {max_slots}")
back_button = Button(WIDTH // 2 - 150, HEIGHT - 70, 300, 50, "Начать игру",
action=lambda: start_game_with_abilities())
# Формируем описание уровня
level_description = ""
if mode.startswith("level"):
level_num = int(mode[5:])
info = LEVEL_INFO[level_num]
target = info["target_kills"]
enemies_list = []
for enemy_type, sub_type, count in info["initial_enemies"]:
name = RU_NAMES_SNAKES.get(sub_type, sub_type)
enemies_list.append(f"{name} (x{count})")
enemies_str = ", ".join(enemies_list)
level_description = f"Уровень {level_num}: пережить {target} врагов. Враги: {enemies_str}."
if info["modifiers"].get("god_of_torches"):
level_description += " Усложнение: Бог факелов."
if "special_spawn_limit" in info:
limit_str = ", ".join(
[f"{RU_NAMES_SNAKES.get(k, k)} ≤ {v}" for k, v in info["special_spawn_limit"].items()])
level_description += f" Лимит: {limit_str}."
while current_state == "ability_selection":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
mouse_x, mouse_y = event.pos
for idx, ab_info in enumerate(ABILITY_INFO):
rect = pygame.Rect(WIDTH // 2 - 300, 150 + idx * 45, 30, 30)
if rect.collidepoint(mouse_x, mouse_y):
selected[idx] = not selected[idx]
back_button.handle_event(event)
screen.fill(MENU_BG)
title = font.render("Выберите способности", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 50))
# Отображение описания уровня
if level_description:
# Разбиваем длинную строку на несколько, если нужно
words = level_description.split()
lines = []
current_line = ""
for word in words:
if len(current_line) + len(word) + 1 <= 60:
if current_line:
current_line += " "
current_line += word
else:
lines.append(current_line)
current_line = word
if current_line:
lines.append(current_line)
y_desc = 90
for line in lines:
desc_surf = small_font.render(line, True, (50, 50, 150))
screen.blit(desc_surf, (WIDTH // 2 - desc_surf.get_width() // 2, y_desc))
y_desc += 22
y_start = y_desc + 10
else:
y_start = 150
total_slots = sum(ABILITY_INFO[idx]["slots"] for idx, sel in selected.items() if sel)
slot_text = f"Занято ячеек: {total_slots} / {max_slots if max_slots != float('inf') else '∞'}"
slot_color = BLACK if total_slots <= max_slots else RED_SNAKE
slot_surf = small_font.render(slot_text, True, slot_color)
screen.blit(slot_surf, (WIDTH // 2 - slot_surf.get_width() // 2, y_start - 10))
y = y_start
for idx, ab_info in enumerate(ABILITY_INFO):
row_rect = pygame.Rect(WIDTH // 2 - 320, y, 640, 40)
pygame.draw.rect(screen, (220, 220, 220), row_rect, border_radius=5)
cb_rect = pygame.Rect(WIDTH // 2 - 300, y + 5, 30, 30)
pygame.draw.rect(screen, BLACK, cb_rect, 2)
if selected[idx]:
pygame.draw.line(screen, BLACK, (cb_rect.left + 4, cb_rect.centery),
(cb_rect.centerx, cb_rect.bottom - 4), 4)
pygame.draw.line(screen, BLACK, (cb_rect.centerx, cb_rect.bottom - 4),
(cb_rect.right - 4, cb_rect.top + 4), 4)
name_surf = small_font.render(f"{ab_info['name']} (клавиша {ab_info['key']}) - {ab_info['slots']} яч.",
True, BLACK)
screen.blit(name_surf, (WIDTH // 2 - 250, y + 10))
desc_surf = micro_font.render(ab_info["description"], True, (50, 50, 50))
screen.blit(desc_surf, (WIDTH // 2 - 250, y + 25))
y += 45
back_button.draw(screen)
pygame.display.flip()
clock.tick(FPS)
# ---------------------- РГРОВЫЕ РЕЖРРњР« ----------------------
def show_game():
global current_state, survival_records
is_survival = game_mode == "snakes"
record_turns = survival_records.get("snakes", 0) if is_survival else 0
abilities = selected_abilities.copy()
for name, data in abilities.items():
data["cooldown"] = 0
data["uses"] = data["max_uses"]
bird_x, bird_y = GRID_SIZE // 2, GRID_SIZE // 2
last_direction = 'up'
snakes = []
fireballs = []
invisible_clouds = [] # для газа бело-серой змеи
turn_count = 0
red_snakes_killed = 0
black_snakes_killed = 0
game_over = False
turn_state = 'player'
player_moved_this_turn = False
killed_counts = {key: 0 for key in SNAKE_INFO.keys()}
invulnerable_timer = 0
double_turn_active = False
moves_in_turn = 0
running_game = True
while running_game and current_state == "game":
for event in pygame.event.get():
if event.type == pygame.QUIT:
current_state = "menu"
running_game = False
elif event.type == pygame.KEYDOWN and not game_over:
if turn_state == 'player' and not player_moved_this_turn:
# Способность "Метод скипа" (клавиша 7)
if event.key == pygame.K_7 and "Метод скипа" in abilities:
data = abilities["Метод скипа"]
if data["cooldown"] == 0 and data["uses"] > 0:
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
# пропускаем ход игрока, сразу переходим к змеям
elif event.key == pygame.K_1 and "Гэмблинг" in abilities:
data = abilities["Гэмблинг"]
if data["cooldown"] == 0 and data["uses"] > 0:
occupied = set()
for snake in snakes:
occupied.update(snake.get_all_positions())
for fb in fireballs:
if fb.active:
occupied.add((fb.x, fb.y))
for ic in invisible_clouds:
occupied.update(ic.get_affected_cells())
free_cells = [(x, y) for x in range(GRID_SIZE) for y in range(GRID_SIZE) if
(x, y) not in occupied]
if free_cells:
bird_x, bird_y = random.choice(free_cells)
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if check_collision(bird_x, bird_y, snakes, fireballs, invisible_clouds):
game_over = True
elif event.key == pygame.K_2 and "Освежитель" in abilities:
data = abilities["Освежитель"]
if data["cooldown"] == 0 and data["uses"] > 0:
for ic in invisible_clouds[:]:
if abs(ic.x - bird_x) <= 3 and abs(ic.y - bird_y) <= 3:
invisible_clouds.remove(ic)
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_3 and "Айсаир" in abilities:
data = abilities["Айсаир"]
if data["cooldown"] == 0 and data["uses"] > 0:
for fb in fireballs[:]:
if abs(fb.x - bird_x) + abs(fb.y - bird_y) <= 3:
if fb.color == PURPLE_FIRE:
fb.color = (255, 69, 0)
fb.speed = 1 # было 2
elif fb.color == (255, 69, 0):
fb.color = (100, 255, 100)
fb.speed = 0.5 # было 1
elif fb.color == (100, 255, 100):
fb.active = False
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_4 and "Мощные крылья" in abilities:
data = abilities["Мощные крылья"]
if data["cooldown"] == 0 and data["uses"] > 0:
dx, dy = 0, 0
if last_direction == 'up':
dy = -3
elif last_direction == 'down':
dy = 3
elif last_direction == 'left':
dx = -3
elif last_direction == 'right':
dx = 3
new_x = max(0, min(GRID_SIZE - 1, bird_x + dx))
new_y = max(0, min(GRID_SIZE - 1, bird_y + dy))
bird_x, bird_y = new_x, new_y
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if check_collision(bird_x, bird_y, snakes, fireballs, invisible_clouds):
game_over = True
elif event.key == pygame.K_5 and "Ремнант" in abilities:
data = abilities["Ремнант"]
if data["cooldown"] == 0 and data["uses"] > 0:
invulnerable_timer = 4
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_6 and "The world" in abilities:
data = abilities["The world"]
if data["cooldown"] == 0 and data["uses"] > 0:
double_turn_active = True
moves_in_turn = 0
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
if not player_moved_this_turn:
old_x, old_y = bird_x, bird_y
if event.key == pygame.K_w and bird_y > 0:
bird_y -= 1
last_direction = 'up'
elif event.key == pygame.K_s and bird_y < GRID_SIZE - 1:
bird_y += 1
last_direction = 'down'
elif event.key == pygame.K_a and bird_x > 0:
bird_x -= 1
last_direction = 'left'
elif event.key == pygame.K_d and bird_x < GRID_SIZE - 1:
bird_x += 1
last_direction = 'right'
if (bird_x, bird_y) != (old_x, old_y):
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision(bird_x, bird_y, snakes, fireballs, invisible_clouds):
game_over = True
if double_turn_active and player_moved_this_turn:
moves_in_turn += 1
if moves_in_turn >= 7:
double_turn_active = False
turn_state = 'snakes'
else:
player_moved_this_turn = False
elif player_moved_this_turn:
turn_state = 'snakes'
elif event.type == pygame.KEYDOWN and game_over:
if event.key == pygame.K_r:
show_game()
return
elif event.key == pygame.K_q:
current_state = "menu"
running_game = False
if not game_over and turn_state == 'snakes':
for snake in snakes[:]:
snake.move(bird_x, bird_y, fireballs, invisible_clouds) # передаём облака для бело-серой
for fb in fireballs[:]:
fb.move()
if not fb.active:
fireballs.remove(fb)
for ic in invisible_clouds[:]:
ic.update()
if not ic.active:
invisible_clouds.remove(ic)
snakes_to_remove = []
for snake in snakes:
if snake.is_completely_off_grid():
snakes_to_remove.append(snake)
if snake.snake_type == 'red':
red_snakes_killed += 1
elif snake.snake_type == 'black':
black_snakes_killed += 1
killed_counts[snake.snake_type] += 1
for snake in snakes_to_remove:
snakes.remove(snake)
MAX_SNAKES = 12
if len(snakes) < MAX_SNAKES:
available_types = []
if red_snakes_killed == 0:
available_types.extend(['green', 'dark_green', 'bright_green', 'stripe_green', 'swamp_green'])
else:
if red_snakes_killed == 1:
available_types.extend(['blue', 'dark_green', 'bright_green', 'stripe_green', 'swamp_green'])
if red_snakes_killed == 2:
available_types.extend(['blue', 'dark_blue', 'bright_green', 'stripe_green', 'swamp_green'])
if red_snakes_killed == 3:
available_types.extend(['blue', 'dark_blue', 'light_blue', 'stripe_green', 'swamp_green'])
if red_snakes_killed == 4:
available_types.extend(['blue', 'dark_blue', 'light_blue', 'stripe_blue', 'swamp_green'])
if red_snakes_killed == 5:
available_types.extend(['blue', 'dark_blue', 'light_blue', 'stripe_blue', 'pink_azure'])
if black_snakes_killed == 2:
available_types.extend(['white', 'stripe_blue', 'pink_azure'])
if black_snakes_killed == 3:
available_types.extend(['white', 'white_purple'])
if black_snakes_killed >= 4:
available_types.extend(['white', 'white_purple', 'white_red'])
if red_snakes_killed < 5 or black_snakes_killed >= 5:
available_types.append('red')
if red_snakes_killed >= 5:
available_types.append('black')
# Бело-серая змея после 5 чёрных
if black_snakes_killed >= 5:
available_types.append('white_gray')
if random.random() < 0.005:
available_types.append('rainbow_snake')
available_types = list(dict.fromkeys(available_types))
if random.random() < 0.35 and available_types:
snake_type = random.choice(available_types)
if snake_type == 'red':
if len([s for s in snakes if s.snake_type == 'red']) < 2 and random.random() < 0.12:
new_snake = create_snake_at_edge('red')
collision = any(new_snake.get_all_positions() & s.get_all_positions() for s in snakes)
if not collision:
snakes.append(new_snake)
elif snake_type == 'black':
if random.random() < 0.08:
new_snake = create_snake_at_edge('black')
collision = any(new_snake.get_all_positions() & s.get_all_positions() for s in snakes)
if not collision:
snakes.append(new_snake)
elif snake_type == 'white_gray':
if random.random() < 0.1:
new_snake = create_snake_at_edge('white_gray')
collision = any(new_snake.get_all_positions() & s.get_all_positions() for s in snakes)
if not collision:
snakes.append(new_snake)
else:
new_snake = create_snake_at_edge(snake_type)
collision = any(new_snake.get_all_positions() & s.get_all_positions() for s in snakes)
if not collision:
snakes.append(new_snake)
if invulnerable_timer <= 0 and check_collision(bird_x, bird_y, snakes, fireballs, invisible_clouds):
game_over = True
for data in abilities.values():
if data["cooldown"] > 0:
data["cooldown"] -= 1
if invulnerable_timer > 0:
invulnerable_timer -= 1
turn_state = 'player'
player_moved_this_turn = False
turn_count += 1
if not game_over and turn_state == 'player' and player_moved_this_turn and not double_turn_active:
turn_state = 'snakes'
screen.fill(WHITE)
draw_grid()
for snake in snakes:
snake.draw(screen)
for fb in fireballs:
fb.draw(screen)
for ic in invisible_clouds:
ic.draw(screen)
draw_bird(screen, bird_x, bird_y, True)
y = 10
ru_names = {
"green": "Зелёная", "dark_green": "Тёмно-зелёная", "bright_green": "Ярко-зелёная",
"stripe_green": "Полосатая зелёная", "swamp_green": "Болотная",
"blue": "Синяя", "dark_blue": "Тёмно-синяя", "light_blue": "Светло-синяя",
"stripe_blue": "Полосатая синяя", "pink_azure": "Розово-лазурная",
"white": "Белая", "white_purple": "Бело-фиолетовая", "white_red": "Бело-красная",
"red": "Красная", "black": "Чёрная", "rainbow_snake": "Радужная", "white_gray": "Бело-серая"
}
for snake_type in SNAKE_INFO_ORDERED:
if killed_counts[snake_type] > 0:
info = SNAKE_INFO[snake_type]
name = ru_names.get(snake_type, snake_type)
text = small_font.render(f"{name}: {killed_counts[snake_type]}", True, info["color"])
screen.blit(text, (10, y))
y += 22
ability_y = 10
for name, data in abilities.items():
info = next((a for a in ABILITY_INFO if a["name"] == name), None)
if info:
key_str = f"[{info['key']}]"
status = f"{data['uses']}/{data['max_uses']}"
if data["cooldown"] > 0:
status += f" (РљР” {data['cooldown']})"
text = micro_font.render(f"{key_str} {name}: {status}", True, BLACK)
screen.blit(text, (WIDTH - 250, ability_y))
ability_y += 20
if invulnerable_timer > 0:
inv_text = micro_font.render(f"Неуязвимость: {invulnerable_timer}", True, (200, 0, 0))
screen.blit(inv_text, (WIDTH - 250, ability_y))
ability_y += 20
if is_survival:
record_text = micro_font.render(f"Рекорд ходов: {record_turns}", True, (0, 100, 0))
screen.blit(record_text, (WIDTH - 250, ability_y))
if game_over:
if is_survival and turn_count > survival_records["snakes"]:
survival_records["snakes"] = turn_count
save_records(survival_records)
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [
font.render("ПОРАЖЕНРР•!", True, RED_SNAKE),
small_font.render("R - рестарт | Q - выход", True, GREEN)
]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
pygame.display.flip()
clock.tick(FPS)
def show_spider_game():
global current_state, survival_records
is_survival = game_mode == "spiders"
record_turns = survival_records.get("spiders", 0) if is_survival else 0
abilities = selected_abilities.copy()
for name, data in abilities.items():
data["cooldown"] = 0
data["uses"] = data["max_uses"]
bird_x, bird_y = GRID_SIZE // 2, GRID_SIZE // 2
last_direction = 'up'
spiders = []
shadows = []
fireballs = []
gas_clouds = []
turn_count = 0
red_spiders_killed = 0
killed_counts = {key: 0 for key in SPIDER_INFO.keys()}
game_over = False
turn_state = 'player'
player_moved_this_turn = False
invulnerable_timer = 0
double_turn_active = False
moves_in_turn = 0
def spawn_spider(spider_type):
info = SPIDER_INFO[spider_type]
size = info["size"]
max_attempts = 50
for _ in range(max_attempts):
x = random.randint(0, GRID_SIZE - size)
y = random.randint(0, GRID_SIZE - size)
occupied = set()
for s in spiders:
occupied.update(s.get_positions())
for sh in shadows:
for i in range(sh.size):
for j in range(sh.size):
occupied.add((sh.x + i, sh.y + j))
collision = False
for i in range(size):
for j in range(size):
if (x + i, y + j) in occupied:
collision = True
break
if collision:
break
if not collision:
shadows.append(Shadow(x, y, size, info["fall_time"], spider_type))
return True
return False
def update_spawn():
nonlocal red_spiders_killed
available = []
if red_spiders_killed < 2:
available.extend(["light_gray", "brown_gray", "dark_gray_red_cross", "dark_gray_green_circle"])
if red_spiders_killed == 2:
available.extend(["big_dark_gray", "dark_gray_red_cross", "dark_gray_green_circle"])
if red_spiders_killed > 2 and red_spiders_killed < 4:
available.extend(["big_dark_gray", "dark_gray_red_cross", "big_dark_gray_green_circle"])
if red_spiders_killed == 4:
available.extend(["big_dark_gray", "white_purple_circle", "big_dark_gray_green_circle"])
if red_spiders_killed >= 5:
available.extend(["big_dark_gray", "white_purple_circle", "big_dark_gray_green_circle", "black_spider"])
if red_spiders_killed < 5:
if random.random() < 0.2:
available.append("dark_red")
else:
if random.random() < 0.05:
available.append("dark_red")
if random.random() < 0.005:
available.append("rainbow_spider")
available = list(dict.fromkeys(available))
red_count = sum(1 for s in spiders if s.spider_type == "dark_red")
if red_count >= 1 and "dark_red" in available:
available.remove("dark_red")
if random.random() < 0.25 and len(spiders) + len(shadows) < 8:
if available:
spider_type = random.choice(available)
spawn_spider(spider_type)
while current_state == "spider_game":
for event in pygame.event.get():
if event.type == pygame.QUIT:
current_state = "menu"
return
elif event.type == pygame.KEYDOWN and not game_over:
if turn_state == 'player' and not player_moved_this_turn:
if event.key == pygame.K_7 and "Метод скипа" in abilities:
data = abilities["Метод скипа"]
if data["cooldown"] == 0 and data["uses"] > 0:
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
elif event.key == pygame.K_1 and "Гэмблинг" in abilities:
data = abilities["Гэмблинг"]
if data["cooldown"] == 0 and data["uses"] > 0:
occupied = set()
for spider in spiders:
occupied.update(spider.get_positions())
for sh in shadows:
for i in range(sh.size):
for j in range(sh.size):
occupied.add((sh.x + i, sh.y + j))
for fb in fireballs:
occupied.update(fb.get_positions())
for gc in gas_clouds:
occupied.update(gc.get_affected_cells())
free_cells = [(x, y) for x in range(GRID_SIZE) for y in range(GRID_SIZE) if
(x, y) not in occupied]
if free_cells:
bird_x, bird_y = random.choice(free_cells)
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision_spider(bird_x, bird_y, spiders,
fireballs, gas_clouds):
game_over = True
elif event.key == pygame.K_2 and "Освежитель" in abilities:
data = abilities["Освежитель"]
if data["cooldown"] == 0 and data["uses"] > 0:
for gc in gas_clouds[:]:
if abs(gc.x - bird_x) <= 3 and abs(gc.y - bird_y) <= 3:
gas_clouds.remove(gc)
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_3 and "Айсаир" in abilities:
data = abilities["Айсаир"]
if data["cooldown"] == 0 and data["uses"] > 0:
for fb in fireballs[:]:
if abs(fb.x - bird_x) + abs(fb.y - bird_y) <= 3:
if fb.color == PURPLE_FIRE:
fb.color = (255, 69, 0)
fb.speed = 1 # было 2
elif fb.color == (255, 69, 0):
fb.color = (100, 255, 100)
fb.speed = 0.5 # было 1
elif fb.color == (100, 255, 100):
fb.active = False
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_4 and "Мощные крылья" in abilities:
data = abilities["Мощные крылья"]
if data["cooldown"] == 0 and data["uses"] > 0:
dx, dy = 0, 0
if last_direction == 'up':
dy = -3
elif last_direction == 'down':
dy = 3
elif last_direction == 'left':
dx = -3
elif last_direction == 'right':
dx = 3
new_x = max(0, min(GRID_SIZE - 1, bird_x + dx))
new_y = max(0, min(GRID_SIZE - 1, bird_y + dy))
bird_x, bird_y = new_x, new_y
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision_spider(bird_x, bird_y, spiders, fireballs,
gas_clouds):
game_over = True
elif event.key == pygame.K_5 and "Ремнант" in abilities:
data = abilities["Ремнант"]
if data["cooldown"] == 0 and data["uses"] > 0:
invulnerable_timer = 4
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_6 and "The world" in abilities:
data = abilities["The world"]
if data["cooldown"] == 0 and data["uses"] > 0:
double_turn_active = True
moves_in_turn = 0
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
if not player_moved_this_turn:
old_x, old_y = bird_x, bird_y
if event.key == pygame.K_w and bird_y > 0:
bird_y -= 1
last_direction = 'up'
elif event.key == pygame.K_s and bird_y < GRID_SIZE - 1:
bird_y += 1
last_direction = 'down'
elif event.key == pygame.K_a and bird_x > 0:
bird_x -= 1
last_direction = 'left'
elif event.key == pygame.K_d and bird_x < GRID_SIZE - 1:
bird_x += 1
last_direction = 'right'
if (bird_x, bird_y) != (old_x, old_y):
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision_spider(bird_x, bird_y, spiders, fireballs,
gas_clouds):
game_over = True
if double_turn_active and player_moved_this_turn:
moves_in_turn += 1
if moves_in_turn >= 2:
double_turn_active = False
turn_state = 'snakes'
else:
player_moved_this_turn = False
elif player_moved_this_turn:
turn_state = 'snakes'
elif event.type == pygame.KEYDOWN and game_over:
if event.key == pygame.K_r:
show_spider_game()
return
elif event.key == pygame.K_q:
current_state = "menu"
return
if not game_over and turn_state == 'snakes':
new_spiders = []
for shadow in shadows[:]:
if shadow.update():
spider = Spider(shadow.x, shadow.y, shadow.spider_type)
if shadow.spider_type == "dark_gray_red_cross":
cx, cy = shadow.x, shadow.y
for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
fireballs.append(SpiderFireball(cx + dx, cy + dy, dx, dy, speed=1))
elif shadow.spider_type == "white_purple_circle":
cx = shadow.x + shadow.size // 2
cy = shadow.y + shadow.size // 2
for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
fireballs.append(SpiderFireball(cx, cy, dx, dy, speed=2, length=1, color=PURPLE_FIRE))
fireballs.append(
SpiderFireball(cx + dx, cy + dy, dx, dy, speed=2, length=1, color=PURPLE_FIRE))
elif shadow.spider_type == "rainbow_spider":
spider.shoot_rainbow(fireballs)
new_spiders.append(spider)
shadows.remove(shadow)
spiders.extend(new_spiders)
for spider in spiders[:]:
spider.update(fireballs, gas_clouds, bird_x, bird_y)
if not spider.active:
spiders.remove(spider)
killed_counts[spider.spider_type] += 1
if spider.spider_type == "dark_red":
red_spiders_killed += 1
for fb in fireballs[:]:
fb.move()
if not fb.active:
fireballs.remove(fb)
for gc in gas_clouds[:]:
gc.update()
if not gc.active:
gas_clouds.remove(gc)
if invulnerable_timer <= 0 and check_collision_spider(bird_x, bird_y, spiders, fireballs, gas_clouds):
game_over = True
update_spawn()
for data in abilities.values():
if data["cooldown"] > 0:
data["cooldown"] -= 1
if invulnerable_timer > 0:
invulnerable_timer -= 1
turn_state = 'player'
player_moved_this_turn = False
turn_count += 1
if not game_over and turn_state == 'player' and player_moved_this_turn and not double_turn_active:
turn_state = 'snakes'
for row in range(GRID_SIZE):
for col in range(GRID_SIZE):
rect = pygame.Rect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE)
if (row + col) % 2 == 0:
pygame.draw.rect(screen, (40, 40, 50), rect)
else:
pygame.draw.rect(screen, (30, 30, 40), rect)
pygame.draw.rect(screen, (100, 100, 120), rect, 1)
for shadow in shadows:
shadow.draw(screen)
for spider in spiders:
spider.draw(screen)
for fb in fireballs:
fb.draw(screen)
for gc in gas_clouds:
gc.draw(screen)
draw_bird(screen, bird_x, bird_y, True)
y = 10
ru_names = {
"light_gray": "Светло-серый", "brown_gray": "Коричнево-серый",
"dark_gray_red_cross": "Тёмно-серый с крестом", "dark_gray_green_circle": "Тёмно-серый с кругом",
"dark_red": "Тёмно-красный", "big_dark_gray": "Большой тёмно-серый",
"big_dark_gray_green_circle": "Большой с зелёным кругом", "black_spider": "Чёрный",
"white_purple_circle": "Белый с фиолетовым", "rainbow_spider": "Радужный"
}
for spider_type in SPIDER_INFO_ORDERED:
if killed_counts[spider_type] > 0:
name = ru_names.get(spider_type, spider_type)
text = small_font.render(f"{name}: {killed_counts[spider_type]}", True,
SPIDER_INFO[spider_type]["color"])
screen.blit(text, (10, y))
y += 22
ability_y = 10
for name, data in abilities.items():
info = next((a for a in ABILITY_INFO if a["name"] == name), None)
if info:
key_str = f"[{info['key']}]"
status = f"{data['uses']}/{data['max_uses']}"
if data["cooldown"] > 0:
status += f" (РљР” {data['cooldown']})"
text = micro_font.render(f"{key_str} {name}: {status}", True, BLACK)
screen.blit(text, (WIDTH - 250, ability_y))
ability_y += 20
if invulnerable_timer > 0:
inv_text = micro_font.render(f"Неуязвимость: {invulnerable_timer}", True, (200, 0, 0))
screen.blit(inv_text, (WIDTH - 250, ability_y))
ability_y += 20
if is_survival:
record_text = micro_font.render(f"Рекорд ходов: {record_turns}", True, (0, 100, 0))
screen.blit(record_text, (WIDTH - 250, ability_y))
if game_over:
if is_survival and turn_count > survival_records["spiders"]:
survival_records["spiders"] = turn_count
save_records(survival_records)
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [
font.render("ПОРАЖЕНРР•!", True, RED_SNAKE),
small_font.render("R - рестарт | Q - выход", True, GREEN)
]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
pygame.display.flip()
clock.tick(FPS)
def show_slime_game():
global current_state, survival_records
is_survival = game_mode == "slimes"
record_turns = survival_records.get("slimes", 0) if is_survival else 0
abilities = selected_abilities.copy()
for name, data in abilities.items():
data["cooldown"] = 0
data["uses"] = data["max_uses"]
bird_x, bird_y = GRID_SIZE // 2, GRID_SIZE // 2
last_direction = 'up'
slimes = []
slime_balls = []
gas_clouds = []
invisible_clouds = []
lava_cells_temp = []
turn_count = 0
killed_counts = {key: 0 for key in SLIME_INFO.keys()}
game_over = False
turn_state = 'player'
player_moved_this_turn = False
invulnerable_timer = 0
double_turn_active = False
moves_in_turn = 0
black_slimes_killed = 0 # для спавна жёлтых
def update_spawn():
nonlocal black_slimes_killed
if len(slimes) < 8 and random.random() < 0.3:
available_types = [
"green_slime_jump1", "green_slime_jump2", "dark_green_slime",
"red_slime", "purple_blue_slime", "black_slime"
]
# Оранжевые – только после убийства хотя бы одного чёрного
if black_slimes_killed >= 1:
available_types = [
"bright_orange_slime",
"red_slime", "black_slime"
]
# Жёлтые – после 2 чёрных
if black_slimes_killed >= 2:
available_types = [
"yellow_slime", "bright_orange_slime",
"red_slime", "black_slime"
]
if random.random() < 0.005:
slime_type = "rainbow_slime"
else:
slime_type = random.choice(available_types)
new_slime = spawn_slime(slime_type, lava_cells_list=lava_cells_temp)
occupied = {s.get_pos() for s in slimes}
if new_slime.get_pos() not in occupied:
slimes.append(new_slime)
while current_state == "slime_game":
for event in pygame.event.get():
if event.type == pygame.QUIT:
current_state = "menu"
return
elif event.type == pygame.KEYDOWN and not game_over:
if turn_state == 'player' and not player_moved_this_turn:
if event.key == pygame.K_7 and "Метод скипа" in abilities:
data = abilities["Метод скипа"]
if data["cooldown"] == 0 and data["uses"] > 0:
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
elif event.key == pygame.K_1 and "Гэмблинг" in abilities:
data = abilities["Гэмблинг"]
if data["cooldown"] == 0 and data["uses"] > 0:
occupied = set()
for slime in slimes:
occupied.add(slime.get_pos())
for ball in slime_balls:
if ball.active:
occupied.add((ball.x, ball.y))
for gc in gas_clouds:
occupied.update(gc.get_affected_cells())
for ic in invisible_clouds:
occupied.update(ic.get_affected_cells())
for lava in lava_cells_temp:
if lava.active:
occupied.add((lava.x, lava.y))
free_cells = [(x, y) for x in range(GRID_SIZE) for y in range(GRID_SIZE) if
(x, y) not in occupied]
if free_cells:
bird_x, bird_y = random.choice(free_cells)
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision_slime(bird_x, bird_y, slimes,
slime_balls, gas_clouds,
invisible_clouds, lava_cells_temp):
game_over = True
elif event.key == pygame.K_2 and "Освежитель" in abilities:
data = abilities["Освежитель"]
if data["cooldown"] == 0 and data["uses"] > 0:
for gc in gas_clouds[:]:
if abs(gc.x - bird_x) <= 3 and abs(gc.y - bird_y) <= 3:
gas_clouds.remove(gc)
for ic in invisible_clouds[:]:
if abs(ic.x - bird_x) <= 3 and abs(ic.y - bird_y) <= 3:
invisible_clouds.remove(ic)
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_3 and "Айсаир" in abilities:
data = abilities["Айсаир"]
if data["cooldown"] == 0 and data["uses"] > 0:
for fb in fireballs[:]:
if abs(fb.x - bird_x) + abs(fb.y - bird_y) <= 3:
if fb.color == PURPLE_FIRE:
fb.color = (255, 69, 0)
fb.speed = 1 # было 2
elif fb.color == (255, 69, 0):
fb.color = (100, 255, 100)
fb.speed = 0.5 # было 1
elif fb.color == (100, 255, 100):
fb.active = False
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_4 and "Мощные крылья" in abilities:
data = abilities["Мощные крылья"]
if data["cooldown"] == 0 and data["uses"] > 0:
dx, dy = 0, 0
if last_direction == 'up':
dy = -3
elif last_direction == 'down':
dy = 3
elif last_direction == 'left':
dx = -3
elif last_direction == 'right':
dx = 3
new_x = max(0, min(GRID_SIZE - 1, bird_x + dx))
new_y = max(0, min(GRID_SIZE - 1, bird_y + dy))
bird_x, bird_y = new_x, new_y
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision_slime(bird_x, bird_y, slimes, slime_balls,
gas_clouds, invisible_clouds, lava_cells_temp):
game_over = True
elif event.key == pygame.K_5 and "Ремнант" in abilities:
data = abilities["Ремнант"]
if data["cooldown"] == 0 and data["uses"] > 0:
invulnerable_timer = 4
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_6 and "The world" in abilities:
data = abilities["The world"]
if data["cooldown"] == 0 and data["uses"] > 0:
double_turn_active = True
moves_in_turn = 0
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
if not player_moved_this_turn:
old_x, old_y = bird_x, bird_y
if event.key == pygame.K_w and bird_y > 0:
bird_y -= 1
last_direction = 'up'
elif event.key == pygame.K_s and bird_y < GRID_SIZE - 1:
bird_y += 1
last_direction = 'down'
elif event.key == pygame.K_a and bird_x > 0:
bird_x -= 1
last_direction = 'left'
elif event.key == pygame.K_d and bird_x < GRID_SIZE - 1:
bird_x += 1
last_direction = 'right'
if (bird_x, bird_y) != (old_x, old_y):
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision_slime(bird_x, bird_y, slimes, slime_balls,
gas_clouds, invisible_clouds, lava_cells_temp):
game_over = True
if double_turn_active and player_moved_this_turn:
moves_in_turn += 1
if moves_in_turn >= 2:
double_turn_active = False
turn_state = 'snakes'
else:
player_moved_this_turn = False
elif player_moved_this_turn:
turn_state = 'snakes'
elif event.type == pygame.KEYDOWN and game_over:
if event.key == pygame.K_r:
show_slime_game()
return
elif event.key == pygame.K_q:
current_state = "menu"
return
if not game_over and turn_state == 'snakes':
new_slimes = []
for slime in slimes[:]:
slime.update(slime_balls, gas_clouds, invisible_clouds, new_slimes, lava_cells_temp)
if not slime.active:
slimes.remove(slime)
killed_counts[slime.slime_type] += 1
if slime.slime_type == "black_slime":
black_slimes_killed += 1
slimes.extend(new_slimes)
for ball in slime_balls[:]:
ball.update()
if not ball.active:
slime_balls.remove(ball)
for gc in gas_clouds[:]:
gc.update()
if not gc.active:
gas_clouds.remove(gc)
for ic in invisible_clouds[:]:
ic.update()
if not ic.active:
invisible_clouds.remove(ic)
for lava in lava_cells_temp[:]:
lava.update()
if not lava.active:
lava_cells_temp.remove(lava)
if invulnerable_timer <= 0 and check_collision_slime(bird_x, bird_y, slimes, slime_balls, gas_clouds,
invisible_clouds, lava_cells_temp):
game_over = True
update_spawn()
for data in abilities.values():
if data["cooldown"] > 0:
data["cooldown"] -= 1
if invulnerable_timer > 0:
invulnerable_timer -= 1
turn_state = 'player'
player_moved_this_turn = False
turn_count += 1
if not game_over and turn_state == 'player' and player_moved_this_turn and not double_turn_active:
turn_state = 'snakes'
for row in range(GRID_SIZE):
for col in range(GRID_SIZE):
rect = pygame.Rect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE)
if (row + col) % 2 == 0:
pygame.draw.rect(screen, LIGHT_BLUE_CELL, rect)
else:
pygame.draw.rect(screen, LIGHT_PINK_CELL, rect)
pygame.draw.rect(screen, BLACK, rect, 1)
for lava in lava_cells_temp:
if lava.active:
lava.draw(screen)
for ic in invisible_clouds:
ic.draw(screen)
for gc in gas_clouds:
gc.draw(screen)
for slime in slimes:
slime.draw(screen, invisible_clouds)
for ball in slime_balls:
ball.draw(screen, invisible_clouds)
bird_visible = True
for ic in invisible_clouds:
if (bird_x, bird_y) in ic.get_affected_cells():
bird_visible = False
break
draw_bird(screen, bird_x, bird_y, bird_visible)
y = 10
ru_names = {
"green_slime_jump1": "Зелёный прыгун 1", "green_slime_jump2": "Зелёный прыгун 2",
"dark_green_slime": "Тёмно-зелёный", "red_slime": "Красный",
"purple_blue_slime": "Фиолетово-синий",
"black_slime": "Чёрный", "dark_gray_slime": "Тёмно-серый", "light_gray_slime": "Светло-серый",
"white_slime": "Белый", "rainbow_slime": "Радужный",
"bright_orange_slime": "Ярко-оранжевый", "yellow_slime": "Жёлтый"
}
for slime_type in SLIME_INFO_ORDERED:
if killed_counts[slime_type] > 0:
name = ru_names.get(slime_type, slime_type)
text = small_font.render(f"{name}: {killed_counts[slime_type]}", True, (0, 0, 0))
screen.blit(text, (10, y))
y += 22
ability_y = 10
for name, data in abilities.items():
info = next((a for a in ABILITY_INFO if a["name"] == name), None)
if info:
key_str = f"[{info['key']}]"
status = f"{data['uses']}/{data['max_uses']}"
if data["cooldown"] > 0:
status += f" (РљР” {data['cooldown']})"
text = micro_font.render(f"{key_str} {name}: {status}", True, BLACK)
screen.blit(text, (WIDTH - 250, ability_y))
ability_y += 20
if invulnerable_timer > 0:
inv_text = micro_font.render(f"Неуязвимость: {invulnerable_timer}", True, (200, 0, 0))
screen.blit(inv_text, (WIDTH - 250, ability_y))
ability_y += 20
if is_survival:
record_text = micro_font.render(f"Рекорд ходов: {record_turns}", True, (0, 100, 0))
screen.blit(record_text, (WIDTH - 250, ability_y))
if game_over:
if is_survival and turn_count > survival_records["slimes"]:
survival_records["slimes"] = turn_count
save_records(survival_records)
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [
font.render("ПОРАЖЕНРР•!", True, RED_SNAKE),
small_font.render("R - рестарт | Q - выход", True, GREEN)
]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
pygame.display.flip()
clock.tick(FPS)
def show_insect_game():
global current_state, survival_records
is_survival = game_mode == "insects"
record_turns = survival_records.get("insects", 0) if is_survival else 0
abilities = selected_abilities.copy()
for name, data in abilities.items():
data["cooldown"] = 0
data["uses"] = data["max_uses"]
bird_x, bird_y = GRID_SIZE // 2, GRID_SIZE // 2
last_direction = 'up'
ants = []
beetles = []
anthills = []
shadows = []
poison_clouds = []
invisible_clouds = []
turn_count = 0
killed_counts = {key: 0 for key in INSECT_INFO.keys()}
game_over = False
turn_state = 'player'
player_moved_this_turn = False
invulnerable_timer = 0
double_turn_active = False
moves_in_turn = 0
def spawn_insect_wrapper(insect_type):
if insect_type == "anthill":
for _ in range(50):
tx = random.randint(1, GRID_SIZE - 4)
ty = random.randint(1, GRID_SIZE - 4)
new_ah = Anthill(tx, ty)
collision = False
for ah in anthills:
if new_ah.get_positions() & ah.get_positions():
collision = True
break
for sh in shadows:
for i in range(sh.size):
for j in range(sh.size):
if (sh.x + i, sh.y + j) in new_ah.get_positions():
collision = True
break
if not collision:
shadows.append(InsectShadow(tx, ty, 3, 5, "anthill"))
return True
return False
else:
obj = spawn_insect(insect_type)
if insect_type == "ant":
ants.append(obj)
else:
beetles.append(obj)
return True
def update_spawn():
if len(ants) + len(beetles) + len(anthills) + len(shadows) < 10 and random.random() < 0.3:
insect_type = random.choice(["ant", "dung_beetle", "stag_beetle"])
if insect_type == "ant":
ants.append(spawn_insect("ant"))
else:
beetles.append(spawn_insect(insect_type))
if random.random() < 0.02 and len(anthills) + len([sh for sh in shadows if sh.insect_type == "anthill"]) < 2:
spawn_insect_wrapper("anthill")
while current_state == "insect_game":
for event in pygame.event.get():
if event.type == pygame.QUIT:
current_state = "menu"
return
elif event.type == pygame.KEYDOWN and not game_over:
if turn_state == 'player' and not player_moved_this_turn:
if event.key == pygame.K_7 and "Метод скипа" in abilities:
data = abilities["Метод скипа"]
if data["cooldown"] == 0 and data["uses"] > 0:
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
elif event.key == pygame.K_1 and "Гэмблинг" in abilities:
data = abilities["Гэмблинг"]
if data["cooldown"] == 0 and data["uses"] > 0:
occupied = set()
for ant in ants:
occupied.add(ant.get_pos())
for beetle in beetles:
occupied.add(beetle.get_pos())
for ah in anthills:
occupied.update(ah.get_positions())
for sh in shadows:
for i in range(sh.size):
for j in range(sh.size):
occupied.add((sh.x + i, sh.y + j))
for pc in poison_clouds:
occupied.add(pc.get_pos())
for ic in invisible_clouds:
occupied.update(ic.get_affected_cells())
free_cells = [(x, y) for x in range(GRID_SIZE) for y in range(GRID_SIZE) if
(x, y) not in occupied]
if free_cells:
bird_x, bird_y = random.choice(free_cells)
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision_insect(bird_x, bird_y, ants, beetles, anthills, poison_clouds, invisible_clouds):
game_over = True
elif event.key == pygame.K_2 and "Освежитель" in abilities:
data = abilities["Освежитель"]
if data["cooldown"] == 0 and data["uses"] > 0:
for pc in poison_clouds[:]:
if abs(pc.x - bird_x) <= 3 and abs(pc.y - bird_y) <= 3:
poison_clouds.remove(pc)
for ic in invisible_clouds[:]:
if abs(ic.x - bird_x) <= 3 and abs(ic.y - bird_y) <= 3:
invisible_clouds.remove(ic)
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_3 and "Айсаир" in abilities:
data = abilities["Айсаир"]
if data["cooldown"] == 0 and data["uses"] > 0:
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_4 and "Мощные крылья" in abilities:
data = abilities["Мощные крылья"]
if data["cooldown"] == 0 and data["uses"] > 0:
dx, dy = 0, 0
if last_direction == 'up':
dy = -3
elif last_direction == 'down':
dy = 3
elif last_direction == 'left':
dx = -3
elif last_direction == 'right':
dx = 3
new_x = max(0, min(GRID_SIZE - 1, bird_x + dx))
new_y = max(0, min(GRID_SIZE - 1, bird_y + dy))
bird_x, bird_y = new_x, new_y
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision_insect(bird_x, bird_y, ants, beetles, anthills, poison_clouds, invisible_clouds):
game_over = True
elif event.key == pygame.K_5 and "Ремнант" in abilities:
data = abilities["Ремнант"]
if data["cooldown"] == 0 and data["uses"] > 0:
invulnerable_timer = 4
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_6 and "The world" in abilities:
data = abilities["The world"]
if data["cooldown"] == 0 and data["uses"] > 0:
double_turn_active = True
moves_in_turn = 0
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
if not player_moved_this_turn:
old_x, old_y = bird_x, bird_y
if event.key == pygame.K_w and bird_y > 0:
bird_y -= 1
last_direction = 'up'
elif event.key == pygame.K_s and bird_y < GRID_SIZE - 1:
bird_y += 1
last_direction = 'down'
elif event.key == pygame.K_a and bird_x > 0:
bird_x -= 1
last_direction = 'left'
elif event.key == pygame.K_d and bird_x < GRID_SIZE - 1:
bird_x += 1
last_direction = 'right'
if (bird_x, bird_y) != (old_x, old_y):
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision_insect(bird_x, bird_y, ants, beetles, anthills, poison_clouds, invisible_clouds):
game_over = True
if double_turn_active and player_moved_this_turn:
moves_in_turn += 1
if moves_in_turn >= 2:
double_turn_active = False
turn_state = 'enemies'
else:
player_moved_this_turn = False
elif player_moved_this_turn:
turn_state = 'enemies'
elif event.type == pygame.KEYDOWN and game_over:
if event.key == pygame.K_r:
show_insect_game()
return
elif event.key == pygame.K_q:
current_state = "menu"
return
if not game_over and turn_state == 'enemies':
new_anthills = []
for shadow in shadows[:]:
if shadow.update():
if shadow.insect_type == "anthill":
ah = Anthill(shadow.x, shadow.y)
new_anthills.append(ah)
shadows.remove(shadow)
anthills.extend(new_anthills)
for ant in ants[:]:
ant.update(bird_x, bird_y, poison_clouds, invisible_clouds)
if not ant.active:
ants.remove(ant)
killed_counts["ant"] += 1
for beetle in beetles[:]:
if isinstance(beetle, DungBeetle):
beetle.update(bird_x, bird_y, poison_clouds)
elif isinstance(beetle, StagBeetle):
beetle.update(bird_x, bird_y, invisible_clouds)
if not beetle.active:
beetles.remove(beetle)
if isinstance(beetle, DungBeetle):
killed_counts["dung_beetle"] += 1
else:
killed_counts["stag_beetle"] += 1
for ah in anthills[:]:
ah.update(ants)
if not ah.active:
anthills.remove(ah)
killed_counts["anthill"] += 1
for pc in poison_clouds[:]:
pc.update()
if not pc.active:
poison_clouds.remove(pc)
for ic in invisible_clouds[:]:
ic.update()
if not ic.active:
invisible_clouds.remove(ic)
if invulnerable_timer <= 0 and check_collision_insect(bird_x, bird_y, ants, beetles, anthills, poison_clouds, invisible_clouds):
game_over = True
update_spawn()
for data in abilities.values():
if data["cooldown"] > 0:
data["cooldown"] -= 1
if invulnerable_timer > 0:
invulnerable_timer -= 1
turn_state = 'player'
player_moved_this_turn = False
turn_count += 1
if not game_over and turn_state == 'player' and player_moved_this_turn and not double_turn_active:
turn_state = 'enemies'
for row in range(GRID_SIZE):
for col in range(GRID_SIZE):
rect = pygame.Rect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE)
if (row + col) % 2 == 0:
pygame.draw.rect(screen, SAND_INSECT_LIGHT, rect)
else:
pygame.draw.rect(screen, SAND_INSECT_DARK, rect)
pygame.draw.rect(screen, BLACK, rect, 1)
for shadow in shadows:
shadow.draw(screen)
for pc in poison_clouds:
pc.draw(screen)
for ic in invisible_clouds:
ic.draw(screen)
for ah in anthills:
ah.draw(screen)
for beetle in beetles:
beetle.draw(screen)
for ant in ants:
ant.draw(screen)
draw_bird(screen, bird_x, bird_y, True)
y = 10
ru_names = {
"ant": "Муравей",
"dung_beetle": "Навозник",
"stag_beetle": "Рогач",
"anthill": "Муравейник"
}
for insect_type in INSECT_INFO_ORDERED:
if killed_counts[insect_type] > 0:
name = ru_names.get(insect_type, insect_type)
text = small_font.render(f"{name}: {killed_counts[insect_type]}", True, INSECT_INFO[insect_type]["color"])
screen.blit(text, (10, y))
y += 22
ability_y = 10
for name, data in abilities.items():
info = next((a for a in ABILITY_INFO if a["name"] == name), None)
if info:
key_str = f"[{info['key']}]"
status = f"{data['uses']}/{data['max_uses']}"
if data["cooldown"] > 0:
status += f" (РљР” {data['cooldown']})"
text = micro_font.render(f"{key_str} {name}: {status}", True, BLACK)
screen.blit(text, (WIDTH - 250, ability_y))
ability_y += 20
if invulnerable_timer > 0:
inv_text = micro_font.render(f"Неуязвимость: {invulnerable_timer}", True, (200, 0, 0))
screen.blit(inv_text, (WIDTH - 250, ability_y))
ability_y += 20
if is_survival:
record_text = micro_font.render(f"Рекорд ходов: {record_turns}", True, (0, 100, 0))
screen.blit(record_text, (WIDTH - 250, ability_y))
if game_over:
if is_survival and turn_count > survival_records["insects"]:
survival_records["insects"] = turn_count
save_records(survival_records)
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [
font.render("ПОРАЖЕНРР•!", True, RED_SNAKE),
small_font.render("R - рестарт | Q - выход", True, GREEN)
]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
pygame.display.flip()
clock.tick(FPS)
def show_fish_game():
global current_state, survival_records
is_survival = game_mode == "fish"
record_turns = survival_records.get("fish", 0) if is_survival else 0
abilities = selected_abilities.copy()
for name, data in abilities.items():
data["cooldown"] = 0
data["uses"] = data["max_uses"]
bird_x, bird_y = GRID_SIZE // 2, GRID_SIZE // 2
last_direction = 'up'
fishes = []
turn_count = 0
killed_counts = {key: 0 for key in FISH_INFO.keys()}
game_over = False
turn_state = 'player'
player_moved_this_turn = False
invulnerable_timer = 0
double_turn_active = False
moves_in_turn = 0
def update_spawn():
if len(fishes) < 6 and random.random() < 0.3:
octopus_count = sum(1 for f in fishes if isinstance(f, Octopus))
available = ["swallow", "swordfish", "shark"]
if octopus_count == 0:
available.append("octopus")
fish_type = random.choice(available)
new_fish = spawn_fish(fish_type)
fishes.append(new_fish)
while current_state == "fish_game":
for event in pygame.event.get():
if event.type == pygame.QUIT:
current_state = "menu"
return
elif event.type == pygame.KEYDOWN and not game_over:
if turn_state == 'player' and not player_moved_this_turn:
if event.key == pygame.K_7 and "Метод скипа" in abilities:
data = abilities["Метод скипа"]
if data["cooldown"] == 0 and data["uses"] > 0:
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
elif event.key == pygame.K_1 and "Гэмблинг" in abilities:
data = abilities["Гэмблинг"]
if data["cooldown"] == 0 and data["uses"] > 0:
occupied = set()
for fish in fishes:
if not fish.underwater:
occupied.update(fish.get_positions())
if isinstance(fish, Octopus) and not fish.underwater:
occupied.update(fish.tentacles)
free_cells = [(x, y) for x in range(GRID_SIZE) for y in range(GRID_SIZE) if
(x, y) not in occupied]
if free_cells:
bird_x, bird_y = random.choice(free_cells)
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision_fish(bird_x, bird_y, fishes):
game_over = True
elif event.key == pygame.K_2 and "Освежитель" in abilities:
data = abilities["Освежитель"]
if data["cooldown"] == 0 and data["uses"] > 0:
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_3 and "Айсаир" in abilities:
data = abilities["Айсаир"]
if data["cooldown"] == 0 and data["uses"] > 0:
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_4 and "Мощные крылья" in abilities:
data = abilities["Мощные крылья"]
if data["cooldown"] == 0 and data["uses"] > 0:
dx, dy = 0, 0
if last_direction == 'up':
dy = -3
elif last_direction == 'down':
dy = 3
elif last_direction == 'left':
dx = -3
elif last_direction == 'right':
dx = 3
new_x = max(0, min(GRID_SIZE - 1, bird_x + dx))
new_y = max(0, min(GRID_SIZE - 1, bird_y + dy))
bird_x, bird_y = new_x, new_y
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision_fish(bird_x, bird_y, fishes):
game_over = True
elif event.key == pygame.K_5 and "Ремнант" in abilities:
data = abilities["Ремнант"]
if data["cooldown"] == 0 and data["uses"] > 0:
invulnerable_timer = 4
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_6 and "The world" in abilities:
data = abilities["The world"]
if data["cooldown"] == 0 and data["uses"] > 0:
double_turn_active = True
moves_in_turn = 0
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
if not player_moved_this_turn:
old_x, old_y = bird_x, bird_y
if event.key == pygame.K_w and bird_y > 0:
bird_y -= 1
last_direction = 'up'
elif event.key == pygame.K_s and bird_y < GRID_SIZE - 1:
bird_y += 1
last_direction = 'down'
elif event.key == pygame.K_a and bird_x > 0:
bird_x -= 1
last_direction = 'left'
elif event.key == pygame.K_d and bird_x < GRID_SIZE - 1:
bird_x += 1
last_direction = 'right'
if (bird_x, bird_y) != (old_x, old_y):
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision_fish(bird_x, bird_y, fishes):
game_over = True
if double_turn_active and player_moved_this_turn:
moves_in_turn += 1
if moves_in_turn >= 2:
double_turn_active = False
turn_state = 'enemies'
else:
player_moved_this_turn = False
elif player_moved_this_turn:
turn_state = 'enemies'
elif event.type == pygame.KEYDOWN and game_over:
if event.key == pygame.K_r:
show_fish_game()
return
elif event.key == pygame.K_q:
current_state = "menu"
return
if not game_over and turn_state == 'enemies':
for fish in fishes[:]:
fish.update(bird_x, bird_y, fishes)
if not fish.active:
fishes.remove(fish)
killed_counts[fish.type] += 1
if invulnerable_timer <= 0 and check_collision_fish(bird_x, bird_y, fishes):
game_over = True
update_spawn()
for data in abilities.values():
if data["cooldown"] > 0:
data["cooldown"] -= 1
if invulnerable_timer > 0:
invulnerable_timer -= 1
turn_state = 'player'
player_moved_this_turn = False
turn_count += 1
if not game_over and turn_state == 'player' and player_moved_this_turn and not double_turn_active:
turn_state = 'enemies'
for row in range(GRID_SIZE):
for col in range(GRID_SIZE):
rect = pygame.Rect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE)
if (row + col) % 2 == 0:
pygame.draw.rect(screen, FISH_LIGHT_BLUE, rect)
else:
pygame.draw.rect(screen, FISH_DARK_BLUE, rect)
pygame.draw.rect(screen, BLACK, rect, 1)
for fish in fishes:
fish.draw(screen)
draw_bird(screen, bird_x, bird_y, True)
y = 10
ru_names = {
"swallow": "Ласточка",
"swordfish": "Меч",
"shark": "Акула",
"octopus": "РћСЃСЊРјРёРЅРѕРі"
}
for fish_type in FISH_INFO_ORDERED:
if killed_counts[fish_type] > 0:
name = ru_names.get(fish_type, fish_type)
text = small_font.render(f"{name}: {killed_counts[fish_type]}", True, FISH_INFO[fish_type]["color"])
screen.blit(text, (10, y))
y += 22
ability_y = 10
for name, data in abilities.items():
info = next((a for a in ABILITY_INFO if a["name"] == name), None)
if info:
key_str = f"[{info['key']}]"
status = f"{data['uses']}/{data['max_uses']}"
if data["cooldown"] > 0:
status += f" (РљР” {data['cooldown']})"
text = micro_font.render(f"{key_str} {name}: {status}", True, BLACK)
screen.blit(text, (WIDTH - 250, ability_y))
ability_y += 20
if invulnerable_timer > 0:
inv_text = micro_font.render(f"Неуязвимость: {invulnerable_timer}", True, (200, 0, 0))
screen.blit(inv_text, (WIDTH - 250, ability_y))
ability_y += 20
if is_survival:
record_text = micro_font.render(f"Рекорд ходов: {record_turns}", True, (0, 100, 0))
screen.blit(record_text, (WIDTH - 250, ability_y))
if game_over:
if is_survival and turn_count > survival_records["fish"]:
survival_records["fish"] = turn_count
save_records(survival_records)
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [
font.render("ПОРАЖЕНРР•!", True, RED_SNAKE),
small_font.render("R - рестарт | Q - выход", True, GREEN)
]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
pygame.display.flip()
clock.tick(FPS)
# ---------------------- ПЕСОЧНРЦА ----------------------
sandbox_settings = {
"style": 0,
"enemies": {
"snakes": {name: {"enabled": False} for name in SNAKE_INFO_ORDERED},
"spiders": {name: {"enabled": False} for name in SPIDER_INFO_ORDERED},
"slimes": {name: {"enabled": False} for name in SLIME_INFO_ORDERED},
"insects": {name: {"enabled": False} for name in INSECT_INFO_ORDERED},
"fish": {name: {"enabled": False} for name in FISH_INFO_ORDERED}
}
}
sandbox_modifiers = {
"paranoia": False,
"mirages": False,
"walls_are_lava": False,
"god_of_torches": False,
"sociophobia": False,
"xenos": False
}
MODIFIERS_INFO = {
"paranoia": "Паранойя: каждый ход на месте прошлого положения героя появляется чёрная птица.",
"mirages": "Миражи: иногда появляются копии игрока, повторяющие его движение 5-10 ходов.",
"walls_are_lava": "Стены — это лава: по краям поля в начале появляются клетки лавы (длина 3 от каждой стены)",
"god_of_torches": "Бог факелов: из краёв карты вылетают красные шары.",
"sociophobia": "Социофобия: врагов спавнится значительно больше.",
"xenos": "Ксеносы: в начале игры в каждом углу карты появляются бессмертные муравейники, спавнящие муравьёв в 2 раза медленнее."
}
def show_sandbox_modifiers():
global current_state, sandbox_modifiers
selected = sandbox_modifiers.copy()
def save_and_return():
nonlocal selected
global current_state
sandbox_modifiers.update(selected)
current_state = "sandbox_menu"
back_button = Button(WIDTH // 2 - 100, HEIGHT - 70, 200, 50, "Назад", action=save_and_return)
while current_state == "sandbox_modifiers":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
mouse_x, mouse_y = event.pos
for i, (key, _) in enumerate(MODIFIERS_INFO.items()):
rect = pygame.Rect(WIDTH // 2 - 300, 150 + i * 50, 30, 30)
if rect.collidepoint(mouse_x, mouse_y):
selected[key] = not selected[key]
back_button.handle_event(event)
screen.fill(MENU_BG)
title = font.render("Выберите усложнения", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 50))
y = 150
for i, (key, desc) in enumerate(MODIFIERS_INFO.items()):
row_rect = pygame.Rect(WIDTH // 2 - 320, y, 640, 45)
pygame.draw.rect(screen, (220, 220, 220), row_rect, border_radius=5)
cb_rect = pygame.Rect(WIDTH // 2 - 300, y + 8, 30, 30)
pygame.draw.rect(screen, BLACK, cb_rect, 2)
if selected[key]:
pygame.draw.line(screen, BLACK, (cb_rect.left + 4, cb_rect.centery),
(cb_rect.centerx, cb_rect.bottom - 4), 4)
pygame.draw.line(screen, BLACK, (cb_rect.centerx, cb_rect.bottom - 4),
(cb_rect.right - 4, cb_rect.top + 4), 4)
name_surf = small_font.render(key.replace('_', ' ').capitalize(), True, BLACK)
screen.blit(name_surf, (WIDTH // 2 - 250, y + 12))
desc_surf = micro_font.render(desc, True, (50, 50, 50))
screen.blit(desc_surf, (WIDTH // 2 - 250, y + 30))
y += 50
back_button.draw(screen)
pygame.display.flip()
clock.tick(FPS)
def show_sandbox_arena_selection():
global current_state, sandbox_settings
style = sandbox_settings["style"]
back_button = Button(WIDTH // 2 - 100, HEIGHT - 70, 200, 50, "Назад", action=lambda: go_to_sandbox_menu())
styles = ["Змеи", "Пауки", "Слизни", "Насекомые", "Рыбы"]
style_buttons = []
for i, name in enumerate(styles):
btn = Button(WIDTH // 2 - 100, 150 + i * 60, 200, 50, name,
action=lambda idx=i: set_style(idx))
style_buttons.append(btn)
def set_style(idx):
nonlocal style
style = idx
sandbox_settings["style"] = style
while current_state == "sandbox_arena":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
back_button.handle_event(event)
for btn in style_buttons:
btn.handle_event(event)
screen.fill(MENU_BG)
title = font.render("Выберите арену", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 50))
for btn in style_buttons:
btn.draw(screen)
back_button.draw(screen)
pygame.display.flip()
def show_sandbox_menu():
global current_state
back_button = Button(WIDTH // 2 - 100, HEIGHT - 70, 200, 50, "Назад", action=go_to_mode_select)
settings_button = Button(WIDTH // 2 - 100, 150, 200, 50, "Настройки врагов",
action=lambda: go_to_sandbox_settings())
modifiers_button = Button(WIDTH // 2 - 100, 220, 200, 50, "Усложнения",
action=lambda: go_to_sandbox_modifiers())
arena_button = Button(WIDTH // 2 - 100, 290, 200, 50, "Выбор арены",
action=lambda: go_to_sandbox_arena())
play_button = Button(WIDTH // 2 - 100, HEIGHT - 190, 200, 50, "Рграть",
action=lambda: start_sandbox_play(sandbox_settings["style"]))
while current_state == "sandbox_menu":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
back_button.handle_event(event)
settings_button.handle_event(event)
modifiers_button.handle_event(event)
arena_button.handle_event(event)
play_button.handle_event(event)
screen.fill(MENU_BG)
settings_button.draw(screen)
modifiers_button.draw(screen)
arena_button.draw(screen)
play_button.draw(screen)
back_button.draw(screen)
pygame.display.flip()
def go_to_sandbox_settings():
global current_state
current_state = "sandbox_settings"
def go_to_sandbox_modifiers():
global current_state
current_state = "sandbox_modifiers"
def go_to_sandbox_arena():
global current_state
current_state = "sandbox_arena"
def show_sandbox_settings():
global current_state
back_button = Button(WIDTH // 2 - 100, HEIGHT - 70, 200, 50, "Назад", action=lambda: go_to_sandbox_menu())
def randomize_enemies():
for cat_key in sandbox_settings["enemies"]:
for name in sandbox_settings["enemies"][cat_key]:
sandbox_settings["enemies"][cat_key][name]["enabled"] = random.choice([True, False])
random_button = Button(WIDTH - 250, HEIGHT - 70, 200, 50, "Случайные враги", action=randomize_enemies)
categories = {
"Змеи": ("snakes", SNAKE_INFO_ORDERED),
"Паук": ("spiders", SPIDER_INFO_ORDERED),
"Слизни": ("slimes", SLIME_INFO_ORDERED),
"Насекомые": ("insects", INSECT_INFO_ORDERED),
"Рыбы": ("fish", FISH_INFO_ORDERED)
}
y_offset = 100
item_height = 35
scroll_offset = 0
enemy_list = []
for cat_name, (cat_key, enemies) in categories.items():
enemy_list.append(("cat", cat_name))
for enemy in enemies:
enemy_list.append(("enemy", cat_key, enemy))
total_height = len(enemy_list) * item_height
max_scroll = max(0, total_height - (HEIGHT - 200))
while current_state == "sandbox_settings":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
mouse_x, mouse_y = event.pos
adjusted_y = mouse_y - y_offset + scroll_offset
idx = adjusted_y // item_height
if 0 <= idx < len(enemy_list):
item = enemy_list[idx]
if item[0] == "enemy":
cat_key, name = item[1], item[2]
item_rect = pygame.Rect(50, y_offset + idx * item_height - scroll_offset, WIDTH - 100,
item_height)
if 100 <= mouse_x <= 130 and item_rect.top <= mouse_y <= item_rect.bottom:
current_val = sandbox_settings["enemies"][cat_key][name]["enabled"]
sandbox_settings["enemies"][cat_key][name]["enabled"] = not current_val
elif event.button == 4:
scroll_offset = max(0, scroll_offset - 20)
elif event.button == 5:
scroll_offset = min(max_scroll, scroll_offset + 20)
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
current_state = "sandbox_menu"
return
back_button.handle_event(event)
random_button.handle_event(event)
screen.fill(MENU_BG)
title = font.render("Выберите врагов для песочницы", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 30))
current_y = y_offset - scroll_offset
for item in enemy_list:
if item[0] == "cat":
pygame.draw.rect(screen, (200, 200, 200), (50, current_y, WIDTH - 100, item_height))
cat_text = font.render(item[1], True, BLACK)
screen.blit(cat_text, (70, current_y + 5))
else:
cat_key, name = item[1], item[2]
enabled = sandbox_settings["enemies"][cat_key][name]["enabled"]
row_color = (240, 240, 240) if enabled else (220, 220, 220)
pygame.draw.rect(screen, row_color, (50, current_y, WIDTH - 100, item_height))
ru_names = {
"green": "Зелёная", "dark_green": "Тёмно-зелёная", "bright_green": "Ярко-зелёная",
"stripe_green": "Полосатая зелёная", "swamp_green": "Болотная",
"blue": "Синяя", "dark_blue": "Тёмно-синяя", "light_blue": "Светло-синяя",
"stripe_blue": "Полосатая синяя", "pink_azure": "Розово-лазурная",
"white": "Белая", "white_purple": "Бело-фиолетовая", "white_red": "Бело-красная",
"red": "Красная", "black": "Чёрная", "rainbow_snake": "Радужная", "white_gray": "Бело-серая",
"light_gray": "Светло-серый паук", "brown_gray": "Коричнево-серый паук",
"dark_gray_red_cross": "Тёмно-серый с крестом", "dark_gray_green_circle": "Тёмно-серый с кругом",
"dark_red": "Тёмно-красный", "big_dark_gray": "Большой тёмно-серый",
"big_dark_gray_green_circle": "Большой с зелёным кругом", "black_spider": "Чёрный паук",
"white_purple_circle": "Белый с фиолетовым", "rainbow_spider": "Радужный паук",
"green_slime_jump1": "Зелёный прыгун 1", "green_slime_jump2": "Зелёный прыгун 2",
"dark_green_slime": "Тёмно-зелёный слизень", "red_slime": "Красный слизень",
"purple_blue_slime": "Фиолетово-синий",
"black_slime": "Чёрный слизень", "dark_gray_slime": "Тёмно-серый слизень",
"light_gray_slime": "Светло-серый слизень", "white_slime": "Белый слизень",
"rainbow_slime": "Радужный слизень", "bright_orange_slime": "Ярко-оранжевый слизень",
"yellow_slime": "Жёлтый слизень",
"ant": "Муравей", "dung_beetle": "Навозник", "stag_beetle": "Рогач", "anthill": "Муравейник",
"swallow": "Рыба-ласточка", "swordfish": "Рыба-меч", "shark": "Акула", "octopus": "Осьминог"
}
display_name = ru_names.get(name, name.replace('_', ' ').capitalize())
name_surf = small_font.render(display_name, True, BLACK)
screen.blit(name_surf, (150, current_y + 5))
checkbox_rect = pygame.Rect(100, current_y + 5, 20, 20)
pygame.draw.rect(screen, BLACK, checkbox_rect, 2)
if enabled:
pygame.draw.line(screen, BLACK, (checkbox_rect.left + 2, checkbox_rect.centery),
(checkbox_rect.centerx, checkbox_rect.bottom - 2), 3)
pygame.draw.line(screen, BLACK, (checkbox_rect.centerx, checkbox_rect.bottom - 2),
(checkbox_rect.right - 2, checkbox_rect.top + 2), 3)
current_y += item_height
if total_height > HEIGHT - 200:
scroll_bar_height = (HEIGHT - 200) * (HEIGHT - 200) / total_height
scroll_bar_y = y_offset + (scroll_offset / max_scroll) * (HEIGHT - 200 - scroll_bar_height)
pygame.draw.rect(screen, (100, 100, 100), (WIDTH - 20, y_offset, 10, HEIGHT - 200))
pygame.draw.rect(screen, (50, 50, 50), (WIDTH - 20, scroll_bar_y, 10, scroll_bar_height))
back_button.draw(screen)
random_button.draw(screen)
pygame.display.flip()
clock.tick(FPS)
def start_sandbox_play(style):
global current_state, game_mode
sandbox_settings["style"] = style
game_mode = "sandbox"
current_state = "ability_selection"
def show_sandbox_play():
global current_state
abilities = selected_abilities.copy()
for name, data in abilities.items():
data["cooldown"] = 0
data["uses"] = data["max_uses"]
style = sandbox_settings["style"]
bird_x, bird_y = GRID_SIZE // 2, GRID_SIZE // 2
last_direction = 'up'
snakes = []
fireballs = []
spiders = []
shadows = []
spider_fireballs = []
gas_clouds = []
slimes = []
slime_balls = []
slime_gas_clouds = []
invisible_clouds = []
lava_cells_temp = []
ants = []
beetles = []
anthills = []
insect_shadows = []
poison_clouds = []
fishes = []
turn_state = 'player'
player_moved_this_turn = False
game_over = False
invulnerable_timer = 0
double_turn_active = False
moves_in_turn = 0
last_bird_pos = (bird_x, bird_y)
black_birds = set()
mirages = []
torch_cooldown = 0
lava_cells_wall = set()
if sandbox_modifiers.get("walls_are_lava", False):
lava_cells_wall = set()
for x in range(GRID_SIZE):
for y in range(3):
lava_cells_wall.add((x, y))
lava_cells_wall.add((x, GRID_SIZE - 1 - y))
for y in range(GRID_SIZE):
for x in range(3):
lava_cells_wall.add((x, y))
lava_cells_wall.add((GRID_SIZE - 1 - x, y))
immortal_anthills = []
if sandbox_modifiers.get("xenos", False):
corners = [(0, 0), (GRID_SIZE-3, 0), (0, GRID_SIZE-3), (GRID_SIZE-3, GRID_SIZE-3)]
for cx, cy in corners:
ah = Anthill(cx, cy)
ah.spawn_interval = 8
immortal_anthills.append(ah)
def check_modifiers_collision(x, y):
if (x, y) in black_birds:
return True
if (x, y) in lava_cells_wall:
return True
for lava in lava_cells_temp:
if lava.active and lava.get_pos() == (x, y):
return True
return False
def get_spawn_chance():
base = 0.25
if sandbox_modifiers.get("sociophobia", False):
base *= 1.8
return min(base, 0.6)
def update_mirages():
nonlocal mirages
new_mirages = []
for mx, my, life, path in mirages:
life -= 1
if life > 0:
if len(path) > 0:
next_pos = path.pop(0)
new_mirages.append((next_pos[0], next_pos[1], life, path))
mirages = new_mirages
player_path = deque(maxlen=10)
def create_mirage_from_path():
if len(player_path) < 2:
return
start_pos = player_path[-1]
n = random.randint(5, min(10, len(player_path)))
path = list(player_path)[-n-1:-1]
if path:
mirages.append((start_pos[0], start_pos[1], len(path), path))
def spawn_black_bird():
if not sandbox_modifiers.get("paranoia", False):
return
nonlocal black_birds
black_birds.clear()
black_birds.add(last_bird_pos)
def spawn_torch_fireballs():
if not sandbox_modifiers.get("god_of_torches", False):
return
nonlocal torch_cooldown
if torch_cooldown <= 0:
if random.random() < 0.4:
side = random.choice(['top', 'bottom', 'left', 'right'])
if side == 'top':
x = random.randint(0, GRID_SIZE-1)
y = 0
dy = 1
dx = 0
elif side == 'bottom':
x = random.randint(0, GRID_SIZE-1)
y = GRID_SIZE-1
dy = -1
dx = 0
elif side == 'left':
x = 0
y = random.randint(0, GRID_SIZE-1)
dx = 1
dy = 0
else:
x = GRID_SIZE-1
y = random.randint(0, GRID_SIZE-1)
dx = -1
dy = 0
fireballs.append(Fireball(x, y, dx, dy, speed=1, color=(255, 69, 0)))
torch_cooldown = 3
else:
torch_cooldown -= 1
while current_state == "sandbox_play":
for event in pygame.event.get():
if event.type == pygame.QUIT:
current_state = "menu"
return
elif event.type == pygame.KEYDOWN and not game_over:
if turn_state == 'player' and not player_moved_this_turn:
if event.key == pygame.K_7 and "Метод скипа" in abilities:
data = abilities["Метод скипа"]
if data["cooldown"] == 0 and data["uses"] > 0:
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
elif event.key == pygame.K_1 and "Гэмблинг" in abilities:
data = abilities["Гэмблинг"]
if data["cooldown"] == 0 and data["uses"] > 0:
occupied = set()
for snake in snakes:
occupied.update(snake.get_all_positions())
for fb in fireballs:
if fb.active:
occupied.add((fb.x, fb.y))
for spider in spiders:
occupied.update(spider.get_positions())
for sh in shadows:
for i in range(sh.size):
for j in range(sh.size):
occupied.add((sh.x + i, sh.y + j))
for fb2 in spider_fireballs:
occupied.update(fb2.get_positions())
for gc in gas_clouds:
occupied.update(gc.get_affected_cells())
for slime in slimes:
occupied.add(slime.get_pos())
for ball in slime_balls:
if ball.active:
occupied.add((ball.x, ball.y))
for gc2 in slime_gas_clouds:
occupied.update(gc2.get_affected_cells())
for ic in invisible_clouds:
occupied.update(ic.get_affected_cells())
for lava in lava_cells_temp:
if lava.active:
occupied.add((lava.x, lava.y))
for ant in ants:
occupied.add(ant.get_pos())
for beetle in beetles:
occupied.add(beetle.get_pos())
for ah in anthills + immortal_anthills:
occupied.update(ah.get_positions())
for sh in insect_shadows:
for i in range(sh.size):
for j in range(sh.size):
occupied.add((sh.x + i, sh.y + j))
for pc in poison_clouds:
occupied.add(pc.get_pos())
for fish in fishes:
if not fish.underwater:
occupied.update(fish.get_positions())
if isinstance(fish, Octopus) and not fish.underwater:
occupied.update(fish.tentacles)
occupied.update(black_birds)
for mx, my, _, _ in mirages:
occupied.add((mx, my))
occupied.update(lava_cells_wall)
free_cells = [(x, y) for x in range(GRID_SIZE) for y in range(GRID_SIZE) if
(x, y) not in occupied]
if free_cells:
bird_x, bird_y = random.choice(free_cells)
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if invulnerable_timer <= 0 and (check_collision(bird_x, bird_y, snakes, fireballs, invisible_clouds) or
check_collision_spider(bird_x, bird_y, spiders,
spider_fireballs, gas_clouds) or
check_collision_slime(bird_x, bird_y, slimes,
slime_balls, slime_gas_clouds,
invisible_clouds, lava_cells_temp) or
check_collision_insect(bird_x, bird_y, ants, beetles, anthills + immortal_anthills, poison_clouds, invisible_clouds) or
check_collision_fish(bird_x, bird_y, fishes) or
check_modifiers_collision(bird_x, bird_y)):
game_over = True
elif event.key == pygame.K_2 and "Освежитель" in abilities:
data = abilities["Освежитель"]
if data["cooldown"] == 0 and data["uses"] > 0:
for gc in gas_clouds[:]:
if abs(gc.x - bird_x) <= 3 and abs(gc.y - bird_y) <= 3:
gas_clouds.remove(gc)
for gc2 in slime_gas_clouds[:]:
if abs(gc2.x - bird_x) <= 3 and abs(gc2.y - bird_y) <= 3:
slime_gas_clouds.remove(gc2)
for ic in invisible_clouds[:]:
if abs(ic.x - bird_x) <= 3 and abs(ic.y - bird_y) <= 3:
invisible_clouds.remove(ic)
for pc in poison_clouds[:]:
if abs(pc.x - bird_x) <= 3 and abs(pc.y - bird_y) <= 3:
poison_clouds.remove(pc)
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_3 and "Айсаир" in abilities:
data = abilities["Айсаир"]
if data["cooldown"] == 0 and data["uses"] > 0:
all_fireballs = fireballs + spider_fireballs + slime_balls
for fb in all_fireballs[:]:
if abs(fb.x - bird_x) + abs(fb.y - bird_y) <= 3:
if fb.color == PURPLE_FIRE:
fb.color = (255, 69, 0)
fb.speed = 1
elif fb.color == (255, 69, 0):
if isinstance(fb, SlimeBall):
fb.color = (100, 255, 100, 200)
else:
fb.color = (100, 255, 100)
fb.speed = 0.5
elif fb.color in [(100, 255, 100), (100, 255, 100, 200)]:
fb.active = False
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_4 and "Мощные крылья" in abilities:
data = abilities["Мощные крылья"]
if data["cooldown"] == 0 and data["uses"] > 0:
dx, dy = 0, 0
if last_direction == 'up':
dy = -3
elif last_direction == 'down':
dy = 3
elif last_direction == 'left':
dx = -3
elif last_direction == 'right':
dx = 3
new_x = max(0, min(GRID_SIZE - 1, bird_x + dx))
new_y = max(0, min(GRID_SIZE - 1, bird_y + dy))
bird_x, bird_y = new_x, new_y
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if invulnerable_timer <= 0 and (check_collision(bird_x, bird_y, snakes, fireballs, invisible_clouds) or
check_collision_spider(bird_x, bird_y, spiders,
spider_fireballs, gas_clouds) or
check_collision_slime(bird_x, bird_y, slimes, slime_balls,
slime_gas_clouds, invisible_clouds, lava_cells_temp) or
check_collision_insect(bird_x, bird_y, ants, beetles, anthills + immortal_anthills, poison_clouds, invisible_clouds) or
check_collision_fish(bird_x, bird_y, fishes) or
check_modifiers_collision(bird_x, bird_y)):
game_over = True
elif event.key == pygame.K_5 and "Ремнант" in abilities:
data = abilities["Ремнант"]
if data["cooldown"] == 0 and data["uses"] > 0:
invulnerable_timer = 4
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_6 and "The world" in abilities:
data = abilities["The world"]
if data["cooldown"] == 0 and data["uses"] > 0:
double_turn_active = True
moves_in_turn = 0
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
if not player_moved_this_turn:
old_x, old_y = bird_x, bird_y
if event.key == pygame.K_w and bird_y > 0:
bird_y -= 1
last_direction = 'up'
elif event.key == pygame.K_s and bird_y < GRID_SIZE - 1:
bird_y += 1
last_direction = 'down'
elif event.key == pygame.K_a and bird_x > 0:
bird_x -= 1
last_direction = 'left'
elif event.key == pygame.K_d and bird_x < GRID_SIZE - 1:
bird_x += 1
last_direction = 'right'
if (bird_x, bird_y) != (old_x, old_y):
player_moved_this_turn = True
if invulnerable_timer <= 0 and (check_collision(bird_x, bird_y, snakes, fireballs, invisible_clouds) or
check_collision_spider(bird_x, bird_y, spiders,
spider_fireballs, gas_clouds) or
check_collision_slime(bird_x, bird_y, slimes, slime_balls,
slime_gas_clouds, invisible_clouds, lava_cells_temp) or
check_collision_insect(bird_x, bird_y, ants, beetles, anthills + immortal_anthills, poison_clouds, invisible_clouds) or
check_collision_fish(bird_x, bird_y, fishes) or
check_modifiers_collision(bird_x, bird_y)):
game_over = True
if double_turn_active and player_moved_this_turn:
moves_in_turn += 1
if moves_in_turn >= 2:
double_turn_active = False
turn_state = 'snakes'
else:
player_moved_this_turn = False
elif player_moved_this_turn:
turn_state = 'snakes'
elif event.type == pygame.KEYDOWN and game_over:
if event.key == pygame.K_r:
show_sandbox_play()
return
elif event.key == pygame.K_q:
current_state = "menu"
return
if not game_over and turn_state == 'snakes':
for snake in snakes[:]:
snake.move(bird_x, bird_y, fireballs, invisible_clouds)
if snake.is_completely_off_grid():
snakes.remove(snake)
for fb in fireballs[:]:
fb.move()
if not fb.active:
fireballs.remove(fb)
new_spiders = []
for shadow in shadows[:]:
if shadow.update():
spider = Spider(shadow.x, shadow.y, shadow.spider_type)
if shadow.spider_type == "dark_gray_red_cross":
cx, cy = shadow.x, shadow.y
for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
spider_fireballs.append(SpiderFireball(cx + dx, cy + dy, dx, dy, speed=1))
elif shadow.spider_type == "white_purple_circle":
cx = shadow.x + shadow.size // 2
cy = shadow.y + shadow.size // 2
for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
spider_fireballs.append(SpiderFireball(cx, cy, dx, dy, speed=2, length=1, color=PURPLE_FIRE))
spider_fireballs.append(SpiderFireball(cx + dx, cy + dy, dx, dy, speed=2, length=1, color=PURPLE_FIRE))
elif shadow.spider_type == "rainbow_spider":
spider.shoot_rainbow(spider_fireballs)
new_spiders.append(spider)
shadows.remove(shadow)
spiders.extend(new_spiders)
for spider in spiders[:]:
spider.update(spider_fireballs, gas_clouds, bird_x, bird_y)
if not spider.active:
spiders.remove(spider)
for fb in spider_fireballs[:]:
fb.move()
if not fb.active:
spider_fireballs.remove(fb)
for gc in gas_clouds[:]:
gc.update()
if not gc.active:
gas_clouds.remove(gc)
new_slimes = []
for slime in slimes[:]:
slime.update(slime_balls, slime_gas_clouds, invisible_clouds, new_slimes, lava_cells_temp)
if not slime.active:
slimes.remove(slime)
slimes.extend(new_slimes)
for ball in slime_balls[:]:
ball.update()
if not ball.active:
slime_balls.remove(ball)
for gc in slime_gas_clouds[:]:
gc.update()
if not gc.active:
slime_gas_clouds.remove(gc)
for ic in invisible_clouds[:]:
ic.update()
if not ic.active:
invisible_clouds.remove(ic)
for lava in lava_cells_temp[:]:
lava.update()
if not lava.active:
lava_cells_temp.remove(lava)
new_anthills = []
for shadow in insect_shadows[:]:
if shadow.update():
if shadow.insect_type == "anthill":
ah = Anthill(shadow.x, shadow.y)
new_anthills.append(ah)
insect_shadows.remove(shadow)
anthills.extend(new_anthills)
for ant in ants[:]:
ant.update(bird_x, bird_y, poison_clouds, invisible_clouds)
if not ant.active:
ants.remove(ant)
for beetle in beetles[:]:
if isinstance(beetle, DungBeetle):
beetle.update(bird_x, bird_y, poison_clouds)
elif isinstance(beetle, StagBeetle):
beetle.update(bird_x, bird_y, invisible_clouds)
if not beetle.active:
beetles.remove(beetle)
for ah in anthills[:]:
ah.update(ants)
if not ah.active:
anthills.remove(ah)
for pc in poison_clouds[:]:
pc.update()
if not pc.active:
poison_clouds.remove(pc)
for fish in fishes[:]:
fish.update(bird_x, bird_y, fishes)
if not fish.active:
fishes.remove(fish)
for ah in immortal_anthills:
ah.timer += 1
if ah.timer % 8 == 0:
possible_positions = []
for dx in range(-1, ah.size+1):
for dy in range(-1, ah.size+1):
nx = ah.x + dx
ny = ah.y + dy
if (dx < 0 or dx >= ah.size or dy < 0 or dy >= ah.size):
if 0 <= nx < GRID_SIZE and 0 <= ny < GRID_SIZE:
possible_positions.append((nx, ny))
if possible_positions:
sx, sy = random.choice(possible_positions)
direction = random.choice(['up', 'down', 'left', 'right'])
ants.append(Ant(sx, sy, direction))
spawn_chance = get_spawn_chance()
total_enemies = len(snakes) + len(spiders) + len(shadows) + len(slimes) + len(ants) + len(beetles) + len(anthills) + len(insect_shadows) + len(fishes)
if total_enemies < 15 and random.random() < spawn_chance:
enabled = []
for name, data in sandbox_settings["enemies"]["snakes"].items():
if data["enabled"]:
enabled.append(("snake", name))
for name, data in sandbox_settings["enemies"]["spiders"].items():
if data["enabled"]:
enabled.append(("spider", name))
for name, data in sandbox_settings["enemies"]["slimes"].items():
if data["enabled"]:
enabled.append(("slime", name))
for name, data in sandbox_settings["enemies"]["insects"].items():
if data["enabled"]:
enabled.append(("insect", name))
for name, data in sandbox_settings["enemies"]["fish"].items():
if data["enabled"]:
enabled.append(("fish", name))
if enabled:
enemy_type, name = random.choice(enabled)
if enemy_type == "snake":
new_snake = create_snake_at_edge(name)
collision = any(new_snake.get_all_positions() & s.get_all_positions() for s in snakes)
if not collision:
snakes.append(new_snake)
elif enemy_type == "spider":
info = SPIDER_INFO[name]
size = info["size"]
max_attempts = 50
for _ in range(max_attempts):
x = random.randint(0, GRID_SIZE - size)
y = random.randint(0, GRID_SIZE - size)
occupied = set()
for s in spiders:
occupied.update(s.get_positions())
for sh in shadows:
for i in range(sh.size):
for j in range(sh.size):
occupied.add((sh.x + i, sh.y + j))
collision = False
for i in range(size):
for j in range(size):
if (x + i, y + j) in occupied:
collision = True
break
if collision:
break
if not collision:
shadows.append(Shadow(x, y, size, info["fall_time"], name))
break
elif enemy_type == "slime":
new_slime = spawn_slime(name, lava_cells_list=lava_cells_temp)
collision = any(new_slime.get_pos() == s.get_pos() for s in slimes)
if not collision:
slimes.append(new_slime)
elif enemy_type == "insect":
if name == "anthill":
for _ in range(50):
tx = random.randint(1, GRID_SIZE - 4)
ty = random.randint(1, GRID_SIZE - 4)
new_ah = Anthill(tx, ty)
collision = False
for ah in anthills:
if new_ah.get_positions() & ah.get_positions():
collision = True
break
for sh in insect_shadows:
for i in range(sh.size):
for j in range(sh.size):
if (sh.x + i, sh.y + j) in new_ah.get_positions():
collision = True
break
if not collision:
insect_shadows.append(InsectShadow(tx, ty, 3, 5, "anthill"))
break
else:
obj = spawn_insect(name)
if name == "ant":
ants.append(obj)
else:
beetles.append(obj)
elif enemy_type == "fish":
new_fish = spawn_fish(name)
fishes.append(new_fish)
spawn_black_bird()
if sandbox_modifiers.get("mirages", False):
if random.random() < 0.05:
create_mirage_from_path()
update_mirages()
spawn_torch_fireballs()
if invulnerable_timer <= 0 and (check_collision(bird_x, bird_y, snakes, fireballs, invisible_clouds) or
check_collision_spider(bird_x, bird_y, spiders, spider_fireballs, gas_clouds) or
check_collision_slime(bird_x, bird_y, slimes, slime_balls, slime_gas_clouds, invisible_clouds, lava_cells_temp) or
check_collision_insect(bird_x, bird_y, ants, beetles, anthills + immortal_anthills, poison_clouds, invisible_clouds) or
check_collision_fish(bird_x, bird_y, fishes) or
check_modifiers_collision(bird_x, bird_y)):
game_over = True
last_bird_pos = (bird_x, bird_y)
player_path.append((bird_x, bird_y))
for data in abilities.values():
if data["cooldown"] > 0:
data["cooldown"] -= 1
if invulnerable_timer > 0:
invulnerable_timer -= 1
turn_state = 'player'
player_moved_this_turn = False
if not game_over and turn_state == 'player' and player_moved_this_turn and not double_turn_active:
turn_state = 'snakes'
for row in range(GRID_SIZE):
for col in range(GRID_SIZE):
rect = pygame.Rect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE)
if style == 0:
color1, color2 = GRAY, LIGHT_GRAY
elif style == 1:
color1, color2 = (40, 40, 50), (30, 30, 40)
elif style == 2:
color1, color2 = LIGHT_BLUE_CELL, LIGHT_PINK_CELL
elif style == 3:
color1, color2 = SAND_INSECT_LIGHT, SAND_INSECT_DARK
else:
color1, color2 = FISH_LIGHT_BLUE, FISH_DARK_BLUE
if (row + col) % 2 == 0:
pygame.draw.rect(screen, color1, rect)
else:
pygame.draw.rect(screen, color2, rect)
if (col, row) in lava_cells_wall:
pygame.draw.rect(screen, (255, 69, 0), rect)
pygame.draw.rect(screen, BLACK, rect, 1)
for lava in lava_cells_temp:
if lava.active:
lava.draw(screen)
for bx, by in black_birds:
if 0 <= bx < GRID_SIZE and 0 <= by < GRID_SIZE:
draw_bird(screen, bx, by, True, black_shadow=True)
for mx, my, _, _ in mirages:
if 0 <= mx < GRID_SIZE and 0 <= my < GRID_SIZE:
draw_bird(screen, mx, my, True)
for ic in invisible_clouds:
ic.draw(screen)
for gc in slime_gas_clouds:
gc.draw(screen)
for gc in gas_clouds:
gc.draw(screen)
for pc in poison_clouds:
pc.draw(screen)
for shadow in shadows:
shadow.draw(screen)
for spider in spiders:
spider.draw(screen)
for fb in spider_fireballs:
fb.draw(screen)
for snake in snakes:
snake.draw(screen)
for fb in fireballs:
fb.draw(screen)
for slime in slimes:
slime.draw(screen, invisible_clouds)
for ball in slime_balls:
ball.draw(screen, invisible_clouds)
for shadow in insect_shadows:
shadow.draw(screen)
for ah in anthills + immortal_anthills:
ah.draw(screen)
for beetle in beetles:
beetle.draw(screen)
for ant in ants:
ant.draw(screen)
for fish in fishes:
fish.draw(screen)
bird_visible = True
for ic in invisible_clouds:
if (bird_x, bird_y) in ic.get_affected_cells():
bird_visible = False
break
draw_bird(screen, bird_x, bird_y, bird_visible)
ability_y = 10
for name, data in abilities.items():
info = next((a for a in ABILITY_INFO if a["name"] == name), None)
if info:
key_str = f"[{info['key']}]"
status = f"{data['uses']}/{data['max_uses']}"
if data["cooldown"] > 0:
status += f" (РљР” {data['cooldown']})"
text = micro_font.render(f"{key_str} {name}: {status}", True, BLACK)
screen.blit(text, (WIDTH - 250, ability_y))
ability_y += 20
if invulnerable_timer > 0:
inv_text = micro_font.render(f"Неуязвимость: {invulnerable_timer}", True, (200, 0, 0))
screen.blit(inv_text, (WIDTH - 250, ability_y))
if game_over:
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [
font.render("ПОРАЖЕНРР•!", True, RED_SNAKE),
small_font.render("R - рестарт | Q - выход", True, GREEN)
]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
pygame.display.flip()
clock.tick(FPS)
# ---------------------- МЕНЮ, БЕСТРРђР РР™, НАСТРОЙКР----------------------
def show_menu():
global current_state
buttons = [
Button(WIDTH // 2 - 100, 150, 200, 50, "Выбор режима", action=lambda: go_to_mode_select()),
Button(WIDTH // 2 - 100, 220, 200, 50, "Бестиарий", action=lambda: go_to_bestiary()),
Button(WIDTH // 2 - 100, 290, 200, 50, "Управление", action=lambda: go_to_controls()),
Button(WIDTH // 2 - 100, 360, 200, 50, "Выбрать скин", action=lambda: go_to_skin_select()),
Button(WIDTH // 2 - 100, 430, 200, 50, "Настройки", action=lambda: go_to_settings()),
Button(WIDTH // 2 - 100, 500, 200, 50, "Выход", action=exit_game)
]
while current_state == "menu":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
for button in buttons:
button.handle_event(event)
screen.fill(MENU_BG)
title_text = font.render("Птичка против Врагов", True, BLACK)
title_rect = title_text.get_rect(center=(WIDTH // 2, 80))
screen.blit(title_text, title_rect)
for button in buttons:
button.draw(screen)
pygame.display.flip()
def show_game_mode_selection():
global current_state
buttons = [
Button(WIDTH // 2 - 100, 150, 200, 50, "РЈСЂРѕРІРЅРё", action=lambda: go_to_level_select()),
Button(WIDTH // 2 - 100, 220, 200, 50, "Рспытания", action=lambda: go_to_challenge_select()),
Button(WIDTH // 2 - 100, 290, 200, 50, "Выживание", action=lambda: go_to_survival_select()),
Button(WIDTH // 2 - 100, 360, 200, 50, "Песочница", action=lambda: go_to_sandbox_menu()),
Button(WIDTH // 2 - 100, 430, 200, 50, "Назад", action=go_to_menu)
]
while current_state == "mode_select":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
for button in buttons:
button.handle_event(event)
screen.fill(MENU_BG)
title_text = font.render("Выберите режим", True, BLACK)
title_rect = title_text.get_rect(center=(WIDTH // 2, 80))
screen.blit(title_text, title_rect)
for button in buttons:
button.draw(screen)
pygame.display.flip()
def show_level_select():
global current_state
back_button = Button(WIDTH // 2 - 100, HEIGHT - 70, 200, 50, "Назад", action=go_to_mode_select)
button_size = 70
spacing = 15
# Глава 1: Змеи (уровни 1-10)
snake_title_y = 100
snake_buttons = []
for i in range(1, 11):
row = (i-1) // 5
col = (i-1) % 5
x = 30 + col * (button_size + spacing)
y = snake_title_y + 30 + row * (button_size + spacing)
btn = Button(x, y, button_size, button_size, str(i),
action=lambda level=i: start_level(level),
color=(100, 150, 200), hover_color=(70, 120, 170))
snake_buttons.append(btn)
# Глава 2: Пауки (уровни 11-20)
spider_title_y = snake_title_y + 2 * (button_size + spacing) + 50
spider_buttons = []
for i in range(11, 21):
row = (i-11) // 5
col = (i-11) % 5
x = 30 + col * (button_size + spacing)
y = spider_title_y + 30 + row * (button_size + spacing)
btn = Button(x, y, button_size, button_size, str(i),
action=lambda level=i: start_level(level),
color=(100, 200, 150), hover_color=(70, 170, 120))
spider_buttons.append(btn)
while current_state == "level_select":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
back_button.handle_event(event)
for btn in snake_buttons + spider_buttons:
btn.handle_event(event)
screen.fill(MENU_BG)
title = font.render("Выберите уровень", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 30))
chapter1 = small_font.render("Глава 1: Змеи (1-10)", True, (0, 80, 0))
screen.blit(chapter1, (WIDTH // 2 - chapter1.get_width() // 2, snake_title_y))
for btn in snake_buttons:
btn.draw(screen)
chapter2 = small_font.render("Глава 2: Пауки (11-20)", True, (0, 80, 0))
screen.blit(chapter2, (WIDTH // 2 - chapter2.get_width() // 2, spider_title_y))
for btn in spider_buttons:
btn.draw(screen)
back_button.draw(screen)
pygame.display.flip()
def show_level_info(level):
global current_state
info = LEVEL_INFO[level]
target_type = info["target_type"]
target_text = ""
if target_type == "kill":
target_text = f"Цель: пережить {info['target_kills']} врагов."
elif target_type == "collect_apples":
target_text = f"Цель: собрать {info['target_apples']} яблок."
elif target_type == "survive_turns":
target_text = f"Цель: прожить {info['target_turns']} ходов."
elif target_type == "visit_borders":
target_text = "Цель: побывать на каждой стороне арены (верх, низ, лево, право)."
elif target_type == "visit_corners":
target_text = "Цель: посетить все 4 угла арены."
elif target_type == "kill_and_collect":
target_text = f"Цель: пережить {info['target_kills']} врагов и собрать {info['target_apples']} яблок."
# Враги
initial = info["initial_enemies"]
ru_spider_names = {
"light_gray": "Светло-серый паук",
"brown_gray": "Коричнево-серый паук",
"dark_gray_red_cross": "Огненный паук (с крестом)",
"dark_gray_green_circle": "Газовый паук",
"dark_red": "Красный паук (2x2)",
"big_dark_gray": "Большой тёмно-серый паук",
"big_dark_gray_green_circle": "Большой газовый паук",
"black_spider": "Чёрный паук",
"white_purple_circle": "Фиолетовый малый паук",
"rainbow_spider": "Радужный паук"
}
enemies_desc = []
for enemy_type, sub_type, count in initial:
if enemy_type == "snake":
name = RU_NAMES_SNAKES.get(sub_type, sub_type)
elif enemy_type == "spider":
name = ru_spider_names.get(sub_type, sub_type)
else:
name = sub_type
enemies_desc.append(f"{name} x{count}")
enemies_str = ", ".join(enemies_desc)
# Модификаторы
modifiers = info.get("modifiers", {})
modifier_descriptions = {
"god_of_torches": "Бог факелов: с краёв карты вылетают красные шары.",
"walls_are_lava": "Стены — это лава: по краям поля лава.",
"fog": "Туман: вы видите только область радиусом 7 клеток вокруг птички."
}
modifier_texts = []
for mod, desc in modifier_descriptions.items():
if modifiers.get(mod):
modifier_texts.append(desc)
modifier_str = " | ".join(modifier_texts) if modifier_texts else "Нет"
# Снаряжение
equipment = info.get("equipment", [])
equipment_str = ", ".join(equipment) if equipment else "Нет"
back_button = Button(WIDTH // 2 - 100, HEIGHT - 70, 200, 50, "Назад", action=go_to_level_select)
start_button = Button(WIDTH // 2 - 100, HEIGHT - 140, 200, 50, "Начать",
action=lambda: _start_level_game(level))
def _start_level_game(lvl):
global current_state
current_state = f"level{lvl}"
while current_state == "level_info":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
back_button.handle_event(event)
start_button.handle_event(event)
screen.fill(MENU_BG)
title = font.render(f"Уровень {level}", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 40))
y = 120
# Цель
screen.blit(small_font.render(target_text, True, BLACK), (50, y))
y += 40
# Начальные враги
screen.blit(small_font.render("Начальные враги:", True, BLACK), (50, y))
y += 30
# перенос строки по словам (для длинных описаний)
words = enemies_str.split()
cur_line = ""
for word in words:
test_line = f"{cur_line} {word}".strip()
if len(test_line) <= 50:
cur_line = test_line
else:
screen.blit(small_font.render(cur_line, True, BLACK), (70, y))
y += 25
cur_line = word
if cur_line:
screen.blit(small_font.render(cur_line, True, BLACK), (70, y))
y += 25
y += 10
# Модификаторы
screen.blit(small_font.render("Модификаторы:", True, BLACK), (50, y))
y += 25
for mod_line in modifier_texts:
screen.blit(micro_font.render(f"• {mod_line}", True, (150, 0, 0)), (70, y))
y += 20
y += 10
# Снаряжение
screen.blit(small_font.render("Снаряжение:", True, BLACK), (50, y))
y += 25
screen.blit(micro_font.render(equipment_str, True, (0, 0, 150)), (70, y))
back_button.draw(screen)
start_button.draw(screen)
pygame.display.flip()
clock.tick(FPS)
# ---------------------- РЈР РћР’РќР ----------------------
current_level = 1 # добавим в глобальные переменные
def start_level(level):
global current_state, selected_abilities, game_mode, current_level
info = LEVEL_INFO[level]
equip_names = info.get("equipment", [])
selected_abilities = {}
for name in equip_names:
# ищем способность по имени в ABILITY_INFO
for ab in ABILITY_INFO:
if ab["name"] == name:
selected_abilities[name] = {
"uses": ab["uses"],
"cooldown": 0,
"slots": ab["slots"],
"key": ab["key"],
"max_uses": ab["uses"],
"max_cooldown": ab["cooldown"]
}
break
game_mode = f"level{level}"
current_level = level
current_state = "level_info"
def show_challenge_select():
global current_state
back_button = Button(WIDTH // 2 - 100, HEIGHT - 70, 200, 50, "Назад", action=go_to_mode_select)
chall1_btn = Button(WIDTH // 2 - 150, 150, 300, 60, "Сбор яблок", action=lambda: start_challenge(1))
chall2_btn = Button(WIDTH // 2 - 150, 230, 300, 60, "Рстинный Р±РѕРі факелов", action=lambda: start_challenge(2))
chall3_btn = Button(WIDTH // 2 - 150, 310, 300, 60, "Сбор яблок 2", action=lambda: start_challenge(3))
chall4_btn = Button(WIDTH // 2 - 150, 390, 300, 60, "Утро натурала", action=lambda: start_challenge(4))
chall5_btn = Button(WIDTH // 2 - 150, 470, 300, 60, "Сбор яблок 3", action=lambda: start_challenge(5))
while current_state == "challenge_select":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
back_button.handle_event(event)
chall1_btn.handle_event(event)
chall2_btn.handle_event(event)
chall3_btn.handle_event(event)
chall4_btn.handle_event(event)
chall5_btn.handle_event(event)
screen.fill(MENU_BG)
title = font.render("Выберите испытание", True, BLACK)
screen.blit(title, (WIDTH//2 - title.get_width()//2, 80))
chall1_btn.draw(screen)
chall2_btn.draw(screen)
chall3_btn.draw(screen)
chall4_btn.draw(screen)
chall5_btn.draw(screen)
back_button.draw(screen)
pygame.display.flip()
def start_challenge(num):
global current_state, selected_abilities
selected_abilities = {}
if num == 1:
current_state = "challenge1"
elif num == 2:
current_state = "challenge2"
elif num == 3:
current_state = "challenge3"
elif num == 4:
current_state = "challenge4"
elif num == 5:
current_state = "challenge5"
def show_survival_select():
global current_state
back_button = Button(WIDTH // 2 - 100, HEIGHT - 70, 200, 50, "Назад", action=go_to_mode_select)
buttons = [
Button(WIDTH // 2 - 100, 150, 200, 50, "Змеи", action=lambda: start_survival("snakes")),
Button(WIDTH // 2 - 100, 220, 200, 50, "Пауки", action=lambda: start_survival("spiders")),
Button(WIDTH // 2 - 100, 290, 200, 50, "Слизни", action=lambda: start_survival("slimes")),
Button(WIDTH // 2 - 100, 360, 200, 50, "Насекомые", action=lambda: start_survival("insects")),
Button(WIDTH // 2 - 100, 430, 200, 50, "Рыбы", action=lambda: start_survival("fish")),
]
while current_state == "survival_select":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
back_button.handle_event(event)
for btn in buttons:
btn.handle_event(event)
screen.fill(MENU_BG)
title = font.render("Выживание", True, BLACK)
screen.blit(title, (WIDTH//2 - title.get_width()//2, 80))
for btn in buttons:
btn.draw(screen)
back_button.draw(screen)
pygame.display.flip()
def start_survival(mode):
global current_state, selected_abilities
selected_abilities = {}
current_state = "ability_selection"
global game_mode
game_mode = mode
show_ability_selection(mode)
# ---------------------- РЈР РћР’РќР ----------------------
def show_level_game(level):
global current_state
info = LEVEL_INFO[level]
enemy_type = info.get("enemy_type", "snake")
arena_style = info.get("arena_style", 0)
target_type = info["target_type"]
target_kills = info.get("target_kills", 0)
target_apples = info.get("target_apples", 0)
target_turns = info.get("target_turns", 0)
max_enemies = info.get("max_enemies", 12)
spawn_pool = info["spawn_pool"]
spawn_chance = 0.3
modifiers = info.get("modifiers", {})
fog_enabled = modifiers.get("fog", False)
# Копируем выданное снаряжение
abilities = selected_abilities.copy()
for name, data in abilities.items():
data["cooldown"] = 0
data["uses"] = data["max_uses"]
bird_x, bird_y = GRID_SIZE // 2, GRID_SIZE // 2
last_direction = 'up'
enemies = []
fireballs = []
invisible_clouds = []
spider_fireballs = []
gas_clouds = []
lava_cells_wall = set()
if modifiers.get("walls_are_lava"):
for x in range(GRID_SIZE):
for y in range(3):
lava_cells_wall.add((x, y))
lava_cells_wall.add((x, GRID_SIZE - 1 - y))
for y in range(GRID_SIZE):
for x in range(3):
lava_cells_wall.add((x, y))
lava_cells_wall.add((GRID_SIZE - 1 - x, y))
# Переменные для целей
apple_pos = None
apples_collected = 0
turn_count = 0
borders_visited = {"top": False, "bottom": False, "left": False, "right": False}
corners_visited = set()
total_killed = 0
game_over = False
victory = False
turn_state = 'player'
player_moved_this_turn = False
invulnerable_timer = 0
double_turn_active = False
moves_in_turn = 0
torch_cooldown = 0
# Рнициализация врагов
for enemy_t, sub_type, count in info["initial_enemies"]:
for _ in range(count):
if enemy_t == "snake":
enemies.append(create_snake_at_edge(sub_type))
elif enemy_t == "spider":
info_sp = SPIDER_INFO[sub_type]
size = info_sp["size"]
fall_time = info_sp["fall_time"]
for _ in range(50):
x = random.randint(0, GRID_SIZE - size)
y = random.randint(0, GRID_SIZE - size)
occupied = set()
for e in enemies:
if isinstance(e, Spider):
occupied.update(e.get_positions())
elif isinstance(e, Shadow):
for i in range(e.size):
for j in range(e.size):
occupied.add((e.x + i, e.y + j))
collision = False
for i in range(size):
for j in range(size):
if (x + i, y + j) in occupied:
collision = True
break
if collision:
break
if not collision:
enemies.append(Shadow(x, y, size, fall_time, sub_type))
break
def spawn_apple():
nonlocal apple_pos
if target_type not in ("collect_apples", "kill_and_collect"):
return
occupied = set()
for e in enemies:
if isinstance(e, Snake):
occupied.update(e.get_all_positions())
elif isinstance(e, Spider):
occupied.update(e.get_positions())
elif isinstance(e, Shadow):
for i in range(e.size):
for j in range(e.size):
occupied.add((e.x + i, e.y + j))
if modifiers.get("walls_are_lava"):
occupied.update(lava_cells_wall)
free = [(x, y) for x in range(GRID_SIZE) for y in range(GRID_SIZE) if (x, y) not in occupied and (x, y) != (bird_x, bird_y)]
if free:
apple_pos = random.choice(free)
else:
apple_pos = None
if target_type in ("collect_apples", "kill_and_collect"):
spawn_apple()
def spawn_enemy_if_needed():
if len(enemies) >= max_enemies:
return
if random.random() > spawn_chance:
return
if enemy_type == "snake":
if "special_spawn_limit" in info:
for snake_type, limit in info["special_spawn_limit"].items():
current_count = sum(1 for e in enemies if isinstance(e, Snake) and e.snake_type == snake_type)
if current_count >= limit and snake_type in spawn_pool:
allowed_pool = [t for t in spawn_pool if t != snake_type]
if not allowed_pool:
return
enemy_t = random.choice(allowed_pool)
else:
enemy_t = random.choice(spawn_pool)
else:
enemy_t = random.choice(spawn_pool)
new_snake = create_snake_at_edge(enemy_t)
collision = any(isinstance(e, Snake) and new_snake.get_all_positions() & e.get_all_positions() for e in enemies)
if not collision:
enemies.append(new_snake)
elif enemy_type == "spider":
if "special_spawn_limit" in info:
for sp_type, limit in info["special_spawn_limit"].items():
current_count = sum(1 for e in enemies if isinstance(e, Spider) and e.spider_type == sp_type)
if current_count >= limit and sp_type in spawn_pool:
allowed_pool = [t for t in spawn_pool if t != sp_type]
if not allowed_pool:
return
sp_type = random.choice(allowed_pool)
else:
sp_type = random.choice(spawn_pool)
else:
sp_type = random.choice(spawn_pool)
info_sp = SPIDER_INFO[sp_type]
size = info_sp["size"]
fall_time = info_sp["fall_time"]
for _ in range(50):
x = random.randint(0, GRID_SIZE - size)
y = random.randint(0, GRID_SIZE - size)
occupied = set()
for e in enemies:
if isinstance(e, Spider):
occupied.update(e.get_positions())
elif isinstance(e, Shadow):
for i in range(e.size):
for j in range(e.size):
occupied.add((e.x + i, e.y + j))
collision = False
for i in range(size):
for j in range(size):
if (x + i, y + j) in occupied:
collision = True
break
if collision:
break
if not collision:
enemies.append(Shadow(x, y, size, fall_time, sp_type))
break
def spawn_torch_fireballs():
nonlocal torch_cooldown
if not modifiers.get("god_of_torches", False):
return
if torch_cooldown <= 0:
if random.random() < 0.4:
side = random.choice(['top', 'bottom', 'left', 'right'])
if side == 'top':
x = random.randint(0, GRID_SIZE-1)
y = 0
dy = 1
dx = 0
elif side == 'bottom':
x = random.randint(0, GRID_SIZE-1)
y = GRID_SIZE-1
dy = -1
dx = 0
elif side == 'left':
x = 0
y = random.randint(0, GRID_SIZE-1)
dx = 1
dy = 0
else:
x = GRID_SIZE-1
y = random.randint(0, GRID_SIZE-1)
dx = -1
dy = 0
fireballs.append(Fireball(x, y, dx, dy, speed=1, color=(255, 69, 0)))
torch_cooldown = 3
else:
torch_cooldown -= 1
def update_enemies():
nonlocal total_killed
if enemy_type == "snake":
snakes = [e for e in enemies if isinstance(e, Snake)]
for snake in snakes:
snake.move(bird_x, bird_y, fireballs, invisible_clouds)
if snake.is_completely_off_grid():
enemies.remove(snake)
total_killed += 1
for fb in fireballs[:]:
fb.move()
if not fb.active:
fireballs.remove(fb)
for ic in invisible_clouds[:]:
ic.update()
if not ic.active:
invisible_clouds.remove(ic)
elif enemy_type == "spider":
new_spiders = []
for shadow in [e for e in enemies if isinstance(e, Shadow)]:
if shadow.update():
spider = Spider(shadow.x, shadow.y, shadow.spider_type)
if shadow.spider_type == "dark_gray_red_cross":
cx, cy = shadow.x, shadow.y
for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
spider_fireballs.append(SpiderFireball(cx + dx, cy + dy, dx, dy, speed=1))
elif shadow.spider_type == "white_purple_circle":
cx = shadow.x + shadow.size // 2
cy = shadow.y + shadow.size // 2
for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
spider_fireballs.append(SpiderFireball(cx, cy, dx, dy, speed=2, length=1, color=PURPLE_FIRE))
spider_fireballs.append(SpiderFireball(cx + dx, cy + dy, dx, dy, speed=2, length=1, color=PURPLE_FIRE))
elif shadow.spider_type == "rainbow_spider":
spider.shoot_rainbow(spider_fireballs)
new_spiders.append(spider)
enemies.remove(shadow)
enemies.extend(new_spiders)
for spider in [e for e in enemies if isinstance(e, Spider)]:
spider.update(spider_fireballs, gas_clouds, bird_x, bird_y)
if not spider.active:
enemies.remove(spider)
total_killed += 1
for fb in spider_fireballs[:]:
fb.move()
if not fb.active:
spider_fireballs.remove(fb)
for gc in gas_clouds[:]:
gc.update()
if not gc.active:
gas_clouds.remove(gc)
def is_any_visible(positions, bird_x, bird_y, fog):
if not fog:
return True
for (px, py) in positions:
if is_cell_visible(px, py, bird_x, bird_y, fog):
return True
return False
def check_collision():
bird_pos = (bird_x, bird_y)
if bird_pos in lava_cells_wall:
return True
for e in enemies:
if isinstance(e, Snake):
if bird_pos in e.get_all_positions():
return True
elif isinstance(e, Spider):
if bird_pos in e.get_positions():
return True
for fb in fireballs:
if fb.active and fb.get_pos() == bird_pos:
return True
for fb in spider_fireballs:
if fb.active and bird_pos in fb.get_positions():
return True
for ic in invisible_clouds:
if ic.lethal and bird_pos in ic.get_affected_cells():
return True
for gc in gas_clouds:
if bird_pos in gc.get_affected_cells():
return True
return False
running = True
while running and current_state == f"level{level}":
# Обработка событий
for event in pygame.event.get():
if event.type == pygame.QUIT:
current_state = "menu"
running = False
elif event.type == pygame.KEYDOWN and not game_over and not victory:
if turn_state == 'player' and not player_moved_this_turn:
# Обработка способностей (клавиши 1-7)
if event.key == pygame.K_7 and "Метод скипа" in abilities:
data = abilities["Метод скипа"]
if data["cooldown"] == 0 and data["uses"] > 0:
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
elif event.key == pygame.K_1 and "Гэмблинг" in abilities:
data = abilities["Гэмблинг"]
if data["cooldown"] == 0 and data["uses"] > 0:
occupied = set()
for e in enemies:
if isinstance(e, Snake):
occupied.update(e.get_all_positions())
elif isinstance(e, Spider):
occupied.update(e.get_positions())
elif isinstance(e, Shadow):
for i in range(e.size):
for j in range(e.size):
occupied.add((e.x + i, e.y + j))
for fb in fireballs:
if fb.active:
occupied.add((fb.x, fb.y))
for fb in spider_fireballs:
if fb.active:
occupied.update(fb.get_positions())
occupied.update(lava_cells_wall)
free_cells = [(x, y) for x in range(GRID_SIZE) for y in range(GRID_SIZE) if (x, y) not in occupied]
if free_cells:
bird_x, bird_y = random.choice(free_cells)
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision():
game_over = True
elif event.key == pygame.K_2 and "Освежитель" in abilities:
data = abilities["Освежитель"]
if data["cooldown"] == 0 and data["uses"] > 0:
for ic in invisible_clouds[:]:
if abs(ic.x - bird_x) <= 3 and abs(ic.y - bird_y) <= 3:
invisible_clouds.remove(ic)
for gc in gas_clouds[:]:
if abs(gc.x - bird_x) <= 3 and abs(gc.y - bird_y) <= 3:
gas_clouds.remove(gc)
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_3 and "Айсаир" in abilities:
data = abilities["Айсаир"]
if data["cooldown"] == 0 and data["uses"] > 0:
all_fb = fireballs + spider_fireballs
for fb in all_fb:
if abs(fb.x - bird_x) + abs(fb.y - bird_y) <= 3:
if fb.color == PURPLE_FIRE:
fb.color = (255, 69, 0)
fb.speed = 1
elif fb.color == (255, 69, 0):
fb.color = (100, 255, 100)
fb.speed = 0.5
elif fb.color == (100, 255, 100):
fb.active = False
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_4 and "Мощные крылья" in abilities:
data = abilities["Мощные крылья"]
if data["cooldown"] == 0 and data["uses"] > 0:
dx, dy = 0, 0
if last_direction == 'up':
dy = -3
elif last_direction == 'down':
dy = 3
elif last_direction == 'left':
dx = -3
elif last_direction == 'right':
dx = 3
new_x = max(0, min(GRID_SIZE - 1, bird_x + dx))
new_y = max(0, min(GRID_SIZE - 1, bird_y + dy))
bird_x, bird_y = new_x, new_y
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
player_moved_this_turn = True
if invulnerable_timer <= 0 and check_collision():
game_over = True
elif event.key == pygame.K_5 and "Ремнант" in abilities:
data = abilities["Ремнант"]
if data["cooldown"] == 0 and data["uses"] > 0:
invulnerable_timer = 4
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
elif event.key == pygame.K_6 and "The world" in abilities:
data = abilities["The world"]
if data["cooldown"] == 0 and data["uses"] > 0:
double_turn_active = True
moves_in_turn = 0
data["uses"] -= 1
data["cooldown"] = data["max_cooldown"]
if not player_moved_this_turn:
old_x, old_y = bird_x, bird_y
if event.key == pygame.K_w and bird_y > 0:
bird_y -= 1
last_direction = 'up'
elif event.key == pygame.K_s and bird_y < GRID_SIZE - 1:
bird_y += 1
last_direction = 'down'
elif event.key == pygame.K_a and bird_x > 0:
bird_x -= 1
last_direction = 'left'
elif event.key == pygame.K_d and bird_x < GRID_SIZE - 1:
bird_x += 1
last_direction = 'right'
if (bird_x, bird_y) != (old_x, old_y):
player_moved_this_turn = True
# Проверка столкновения
if invulnerable_timer <= 0 and check_collision():
game_over = True
else:
# Обновление целей посещения
if bird_x == 0: borders_visited["left"] = True
if bird_x == GRID_SIZE - 1: borders_visited["right"] = True
if bird_y == 0: borders_visited["top"] = True
if bird_y == GRID_SIZE - 1: borders_visited["bottom"] = True
if (bird_x, bird_y) in [(0,0), (0,GRID_SIZE-1), (GRID_SIZE-1,0), (GRID_SIZE-1,GRID_SIZE-1)]:
corners_visited.add((bird_x, bird_y))
# Сбор яблока
if apple_pos and (bird_x, bird_y) == apple_pos:
apples_collected += 1
apple_pos = None
if (target_type in ("collect_apples", "kill_and_collect") and
apples_collected < target_apples):
spawn_apple()
if double_turn_active:
moves_in_turn += 1
if moves_in_turn >= 2:
double_turn_active = False
turn_state = 'enemies'
else:
player_moved_this_turn = False
else:
turn_state = 'enemies'
elif event.type == pygame.KEYDOWN and (game_over or victory):
if event.key == pygame.K_r:
show_level_game(level)
return
elif event.key == pygame.K_q:
current_state = "menu"
running = False
if not game_over and not victory and turn_state == 'player' and player_moved_this_turn and not double_turn_active:
turn_state = 'enemies'
if not game_over and not victory and turn_state == 'enemies':
update_enemies()
spawn_enemy_if_needed()
spawn_torch_fireballs()
if invulnerable_timer <= 0 and check_collision():
game_over = True
# Проверка целей
if target_type == "kill" and total_killed >= target_kills:
victory = True
elif target_type == "collect_apples" and apples_collected >= target_apples:
victory = True
elif target_type == "survive_turns" and turn_count >= target_turns:
victory = True
elif target_type == "visit_borders" and all(borders_visited.values()):
victory = True
elif target_type == "visit_corners" and len(corners_visited) == 4:
victory = True
elif target_type == "kill_and_collect" and total_killed >= target_kills and apples_collected >= target_apples:
victory = True
# Уменьшаем кулдауны способностей и неуязвимости
for data in abilities.values():
if data["cooldown"] > 0:
data["cooldown"] -= 1
if invulnerable_timer > 0:
invulnerable_timer -= 1
turn_state = 'player'
player_moved_this_turn = False
turn_count += 1
# Отрисовка
screen.fill(BLACK) # фон за границами сетки
# Рисуем клетки арены
for row in range(GRID_SIZE):
for col in range(GRID_SIZE):
rect = pygame.Rect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE)
if arena_style == 1:
color1, color2 = (40, 40, 50), (30, 30, 40)
else:
color1, color2 = GRAY, LIGHT_GRAY
if (row + col) % 2 == 0:
base_color = color1
else:
base_color = color2
pygame.draw.rect(screen, base_color, rect)
# Лава
if (col, row) in lava_cells_wall:
pygame.draw.rect(screen, (255, 69, 0), rect)
pygame.draw.rect(screen, BLACK, rect, 1)
# Отрисовка объектов с учетом тумана
def draw_entity(entity, get_positions_func, draw_func):
positions = get_positions_func() if callable(get_positions_func) else get_positions_func
if is_any_visible(positions, bird_x, bird_y, fog_enabled):
draw_func()
for e in enemies:
if isinstance(e, Snake):
if is_any_visible(e.get_all_positions(), bird_x, bird_y, fog_enabled):
e.draw(screen)
elif isinstance(e, Spider):
if is_any_visible(e.get_positions(), bird_x, bird_y, fog_enabled):
e.draw(screen)
elif isinstance(e, Shadow):
pos = {(e.x + i, e.y + j) for i in range(e.size) for j in range(e.size)}
if is_any_visible(pos, bird_x, bird_y, fog_enabled):
e.draw(screen)
for fb in fireballs:
if is_any_visible({fb.get_pos()}, bird_x, bird_y, fog_enabled):
fb.draw(screen)
for fb in spider_fireballs:
if is_any_visible(fb.get_positions(), bird_x, bird_y, fog_enabled):
fb.draw(screen)
for gc in gas_clouds:
if is_any_visible(gc.get_affected_cells(), bird_x, bird_y, fog_enabled):
gc.draw(screen)
for ic in invisible_clouds:
if is_any_visible(ic.get_affected_cells(), bird_x, bird_y, fog_enabled):
ic.draw(screen)
# Яблоко
if apple_pos:
ax, ay = apple_pos
if is_cell_visible(ax, ay, bird_x, bird_y, fog_enabled):
rect = pygame.Rect(ax * CELL_SIZE, ay * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.circle(screen, (255, 0, 0), rect.center, CELL_SIZE // 3)
pygame.draw.circle(screen, BLACK, rect.center, CELL_SIZE // 3, 2)
# Птичка всегда видна
draw_bird(screen, bird_x, bird_y, True)
# Рнтерфейс: счётчики
if target_type in ("kill", "kill_and_collect"):
kill_text = small_font.render(f"Врагов: {total_killed}/{target_kills}", True, BLACK)
screen.blit(kill_text, (10, 10))
if target_type in ("collect_apples", "kill_and_collect"):
apple_text = small_font.render(f"Яблок: {apples_collected}/{target_apples}", True, BLACK)
screen.blit(apple_text, (10, 30))
if target_type == "survive_turns":
turn_text = small_font.render(f"РҐРѕРґ: {turn_count}/{target_turns}", True, BLACK)
screen.blit(turn_text, (10, 10))
# Посещение краёв и углов можно показывать маленькими индикаторами (опционально)
if target_type == "visit_borders":
b_text = small_font.render(f"Края: {sum(borders_visited.values())}/4", True, BLACK)
screen.blit(b_text, (10, 10))
if target_type == "visit_corners":
c_text = small_font.render(f"Углы: {len(corners_visited)}/4", True, BLACK)
screen.blit(c_text, (10, 10))
# Способности
y_abl = 70
for name, data in abilities.items():
info_ab = next((a for a in ABILITY_INFO if a["name"] == name), None)
if info_ab:
key_str = f"[{info_ab['key']}]"
status = f"{data['uses']}/{data['max_uses']}"
if data["cooldown"] > 0:
status += f" (РљР” {data['cooldown']})"
text = micro_font.render(f"{key_str} {name}: {status}", True, BLACK)
screen.blit(text, (WIDTH - 250, y_abl))
y_abl += 20
if invulnerable_timer > 0:
inv_text = micro_font.render(f"Неуязвимость: {invulnerable_timer}", True, (200, 0, 0))
screen.blit(inv_text, (WIDTH - 250, y_abl))
# Затемнение при поражении/победе
if game_over or victory:
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
if game_over:
texts = [font.render("ПОРАЖЕНРР•!", True, RED_SNAKE),
small_font.render("R - рестарт | Q - выход", True, GREEN)]
else:
texts = [font.render("ПОБЕДА!", True, GREEN),
small_font.render("R - заново | Q - выход", True, GREEN)]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
pygame.display.flip()
clock.tick(FPS)
# ---------------------- РСПЫТАНРРЇ ----------------------
def show_challenge1():
global current_state
bird_x, bird_y = GRID_SIZE // 2, GRID_SIZE // 2
last_direction = 'up'
state = {'apples_collected': 0, 'apple_pos': None, 'victory': False, 'game_over': False}
enemies = []
fireballs = []
gas_clouds = []
invisible_clouds = []
turn_count = 0
turn_state = 'player'
player_moved_this_turn = False
# Максимальное количество врагов на поле
MAX_ENEMIES = 10
# Вероятность появления нового врага каждый ход
SPAWN_CHANCE = 0.35
def spawn_apple():
if target_type not in ("collect_apples", "kill_and_collect"):
return None
occupied = set()
for e in enemies:
if isinstance(e, Snake):
occupied.update(e.get_all_positions())
elif isinstance(e, Spider):
occupied.update(e.get_positions())
elif isinstance(e, Shadow):
for i in range(e.size):
for j in range(e.size):
occupied.add((e.x + i, e.y + j))
if modifiers.get("walls_are_lava"):
occupied.update(lava_cells_wall)
free = [(x, y) for x in range(GRID_SIZE) for y in range(GRID_SIZE)
if (x, y) not in occupied and (x, y) != (bird_x, bird_y)]
if free:
return random.choice(free)
return None
if modifiers.get("walls_are_lava"):
occupied.update(lava_cells_wall)
# Свободные клетки — те, что не заняты врагами/лавой и где нет самой птички
free = [(x, y) for x in range(GRID_SIZE) for y in range(GRID_SIZE)
if (x, y) not in occupied and (x, y) != (bird_x, bird_y)]
if free:
apple_pos = random.choice(free)
else:
apple_pos = None
# Начальные враги
enemies.append(create_snake_at_edge("stripe_green"))
enemies.append(create_snake_at_edge("bright_green"))
sh = Shadow(random.randint(0, GRID_SIZE-1), random.randint(0, GRID_SIZE-1), 1, 3, "dark_gray_green_circle")
enemies.append(sh)
enemies.append(spawn_slime("green_slime_jump1"))
enemies.append(spawn_slime("dark_green_slime"))
spawn_apple()
def spawn_enemy_if_needed():
"""Пытается создать нового врага, если их меньше MAX_ENEMIES."""
if len(enemies) >= MAX_ENEMIES:
return
if random.random() > SPAWN_CHANCE:
return
available_types = [
"stripe_green", # змея
"bright_green", # змея
"dark_gray_green_circle",# паук (через тень)
"green_slime_jump1", # слизень
"dark_green_slime" # слизень
]
enemy_type = random.choice(available_types)
if enemy_type in ("stripe_green", "bright_green"):
new_snake = create_snake_at_edge(enemy_type)
# Проверяем, что змея не пересекается с существующими
collision = False
for e in enemies:
if isinstance(e, Snake):
if new_snake.get_all_positions() & e.get_all_positions():
collision = True
break
elif isinstance(e, Spider):
if new_snake.get_all_positions() & e.get_positions():
collision = True
break
elif isinstance(e, Slime):
if any(pos == e.get_pos() for pos in new_snake.get_all_positions()):
collision = True
break
if not collision:
enemies.append(new_snake)
elif enemy_type == "dark_gray_green_circle":
# Создаём тень паука
size = 1
max_attempts = 20
for _ in range(max_attempts):
x = random.randint(0, GRID_SIZE - size)
y = random.randint(0, GRID_SIZE - size)
occupied = set()
for e in enemies:
if isinstance(e, Spider):
occupied.update(e.get_positions())
elif isinstance(e, Shadow):
for i in range(e.size):
for j in range(e.size):
occupied.add((e.x + i, e.y + j))
collision = any((x + i, y + j) in occupied for i in range(size) for j in range(size))
if not collision:
enemies.append(Shadow(x, y, size, 3, enemy_type))
break
else:
# Слизень
new_slime = spawn_slime(enemy_type)
collision = any(isinstance(e, Slime) and e.get_pos() == new_slime.get_pos() for e in enemies)
if not collision:
enemies.append(new_slime)
def update_enemies():
snakes = [e for e in enemies if isinstance(e, Snake)]
spiders = [e for e in enemies if isinstance(e, Spider)]
slimes = [e for e in enemies if isinstance(e, Slime)]
shadows = [e for e in enemies if isinstance(e, Shadow)]
for sh in shadows[:]:
if sh.update():
spider = Spider(sh.x, sh.y, sh.spider_type)
enemies.append(spider)
enemies.remove(sh)
for snake in snakes:
snake.move(bird_x, bird_y, fireballs, invisible_clouds)
if snake.is_completely_off_grid():
enemies.remove(snake)
for spider in spiders:
spider.update(fireballs, gas_clouds, bird_x, bird_y)
if not spider.active:
enemies.remove(spider)
new_slimes = []
for slime in slimes[:]:
slime.update(fireballs, gas_clouds, invisible_clouds, new_slimes)
if not slime.active:
enemies.remove(slime)
enemies.extend(new_slimes)
for fb in fireballs[:]:
if hasattr(fb, 'move'):
fb.move()
elif hasattr(fb, 'update'):
fb.update()
if not fb.active:
fireballs.remove(fb)
for gc in gas_clouds[:]:
gc.update()
if not gc.active:
gas_clouds.remove(gc)
for ic in invisible_clouds[:]:
ic.update()
if not ic.active:
invisible_clouds.remove(ic)
if state['apple_pos'] and (bird_x, bird_y) == state['apple_pos']:
state['apples_collected'] += 1
if state['apples_collected'] >= 10:
state['victory'] = True
else:
spawn_apple()
def check_collision():
bird_pos = (bird_x, bird_y)
for e in enemies:
if isinstance(e, Snake):
if bird_pos in e.get_all_positions():
return True
elif isinstance(e, Spider):
if bird_pos in e.get_positions():
return True
elif isinstance(e, Slime):
if e.get_pos() == bird_pos and e.slime_type not in ("light_gray_slime", "white_slime"):
return True
for fb in fireballs:
if not fb.active:
continue
if hasattr(fb, 'get_positions'):
if bird_pos in fb.get_positions():
return True
elif hasattr(fb, 'get_pos'):
if fb.get_pos() == bird_pos:
return True
for gc in gas_clouds:
if bird_pos in gc.get_affected_cells():
return True
return False
running = True
while running and current_state == "challenge1":
for event in pygame.event.get():
if event.type == pygame.QUIT:
current_state = "menu"
running = False
elif event.type == pygame.KEYDOWN and not state['game_over'] and not state['victory']:
if turn_state == 'player' and not player_moved_this_turn:
old_x, old_y = bird_x, bird_y
if event.key == pygame.K_w and bird_y > 0:
bird_y -= 1
last_direction = 'up'
elif event.key == pygame.K_s and bird_y < GRID_SIZE - 1:
bird_y += 1
last_direction = 'down'
elif event.key == pygame.K_a and bird_x > 0:
bird_x -= 1
last_direction = 'left'
elif event.key == pygame.K_d and bird_x < GRID_SIZE - 1:
bird_x += 1
last_direction = 'right'
if (bird_x, bird_y) != (old_x, old_y):
player_moved_this_turn = True
if check_collision():
state['game_over'] = True
else:
if state['apple_pos'] and (bird_x, bird_y) == state['apple_pos']:
state['apples_collected'] += 1
if state['apples_collected'] >= 10:
state['victory'] = True
else:
spawn_apple()
turn_state = 'enemies'
elif event.type == pygame.KEYDOWN and (state['game_over'] or state['victory']):
if event.key == pygame.K_r:
show_challenge1()
return
elif event.key == pygame.K_q:
current_state = "menu"
running = False
if not state['game_over'] and not state['victory'] and turn_state == 'enemies':
update_enemies()
spawn_enemy_if_needed() # <-- новый спавн
if check_collision():
state['game_over'] = True
turn_state = 'player'
player_moved_this_turn = False
turn_count += 1
# Отрисовка (без изменений)
screen.fill(WHITE)
draw_grid()
for e in enemies:
if isinstance(e, Snake):
e.draw(screen)
elif isinstance(e, Spider):
e.draw(screen)
elif isinstance(e, Slime):
e.draw(screen)
elif isinstance(e, Shadow):
e.draw(screen)
for fb in fireballs:
fb.draw(screen)
for gc in gas_clouds:
gc.draw(screen)
for ic in invisible_clouds:
ic.draw(screen)
if state['apple_pos']:
ax, ay = state['apple_pos']
rect = pygame.Rect(ax * CELL_SIZE, ay * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.circle(screen, (255, 0, 0), rect.center, CELL_SIZE // 3)
draw_bird(screen, bird_x, bird_y, True)
apple_text = small_font.render(f"Яблок: {state['apples_collected']} / 10", True, BLACK)
screen.blit(apple_text, (WIDTH - 200, 10))
if state['game_over']:
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [font.render("ПОРАЖЕНРР•!", True, RED_SNAKE),
small_font.render("R - рестарт | Q - выход", True, GREEN)]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
elif state['victory']:
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [font.render("ПОБЕДА!", True, GREEN),
small_font.render("R - рестарт | Q - выход", True, GREEN)]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
pygame.display.flip()
clock.tick(FPS)
def show_challenge2():
global current_state
bird_x, bird_y = GRID_SIZE // 2, GRID_SIZE // 2
last_direction = 'up'
turn_count = 0
max_turns = 100
state = {'game_over': False, 'victory': False}
turn_state = 'player'
player_moved_this_turn = False
enemies = []
fireballs = []
gas_clouds = []
invisible_clouds = []
torch_cooldown = 0
MAX_ENEMIES = 12
SPAWN_CHANCE = 0.4
# Начальные враги
enemies.append(create_snake_at_edge("stripe_green"))
enemies.append(create_snake_at_edge("stripe_blue"))
enemies.append(create_snake_at_edge("red"))
sh = Shadow(random.randint(0, GRID_SIZE-1), random.randint(0, GRID_SIZE-1), 2, 5, "dark_red")
enemies.append(sh)
enemies.append(spawn_slime("red_slime"))
def spawn_torch_fireballs():
nonlocal torch_cooldown
if torch_cooldown <= 0:
if random.random() < 0.4:
side = random.choice(['top', 'bottom', 'left', 'right'])
if side == 'top':
x = random.randint(0, GRID_SIZE-1)
y = 0
dy = 1
dx = 0
elif side == 'bottom':
x = random.randint(0, GRID_SIZE-1)
y = GRID_SIZE-1
dy = -1
dx = 0
elif side == 'left':
x = 0
y = random.randint(0, GRID_SIZE-1)
dx = 1
dy = 0
else:
x = GRID_SIZE-1
y = random.randint(0, GRID_SIZE-1)
dx = -1
dy = 0
fireballs.append(Fireball(x, y, dx, dy, speed=1, color=(255, 69, 0)))
torch_cooldown = 3
else:
torch_cooldown -= 1
def spawn_enemy_if_needed():
if len(enemies) >= MAX_ENEMIES:
return
if random.random() > SPAWN_CHANCE:
return
available_types = [
"stripe_green",
"stripe_blue",
"red", # красная змея
"dark_red", # паук
"red_slime" # слизень
]
enemy_type = random.choice(available_types)
if enemy_type in ("stripe_green", "stripe_blue", "red"):
new_snake = create_snake_at_edge(enemy_type)
collision = False
for e in enemies:
if isinstance(e, Snake):
if new_snake.get_all_positions() & e.get_all_positions():
collision = True
break
elif isinstance(e, Spider):
if new_snake.get_all_positions() & e.get_positions():
collision = True
break
elif isinstance(e, Slime):
if any(pos == e.get_pos() for pos in new_snake.get_all_positions()):
collision = True
break
if not collision:
enemies.append(new_snake)
elif enemy_type == "dark_red":
size = 2
max_attempts = 20
for _ in range(max_attempts):
x = random.randint(0, GRID_SIZE - size)
y = random.randint(0, GRID_SIZE - size)
occupied = set()
for e in enemies:
if isinstance(e, Spider):
occupied.update(e.get_positions())
elif isinstance(e, Shadow):
for i in range(e.size):
for j in range(e.size):
occupied.add((e.x + i, e.y + j))
collision = any((x + i, y + j) in occupied for i in range(size) for j in range(size))
if not collision:
enemies.append(Shadow(x, y, size, 5, enemy_type))
break
else: # red_slime
new_slime = spawn_slime(enemy_type)
collision = any(isinstance(e, Slime) and e.get_pos() == new_slime.get_pos() for e in enemies)
if not collision:
enemies.append(new_slime)
def update_enemies():
snakes = [e for e in enemies if isinstance(e, Snake)]
spiders = [e for e in enemies if isinstance(e, Spider)]
slimes = [e for e in enemies if isinstance(e, Slime)]
shadows = [e for e in enemies if isinstance(e, Shadow)]
for sh in shadows[:]:
if sh.update():
spider = Spider(sh.x, sh.y, sh.spider_type)
enemies.append(spider)
enemies.remove(sh)
for snake in snakes:
snake.move(bird_x, bird_y, fireballs, invisible_clouds)
if snake.is_completely_off_grid():
enemies.remove(snake)
for spider in spiders:
spider.update(fireballs, gas_clouds, bird_x, bird_y)
if not spider.active:
enemies.remove(spider)
new_slimes = []
for slime in slimes[:]:
slime.update(fireballs, gas_clouds, invisible_clouds, new_slimes)
if not slime.active:
enemies.remove(slime)
enemies.extend(new_slimes)
for fb in fireballs[:]:
if hasattr(fb, 'move'):
fb.move()
elif hasattr(fb, 'update'):
fb.update()
if not fb.active:
fireballs.remove(fb)
for gc in gas_clouds[:]:
gc.update()
if not gc.active:
gas_clouds.remove(gc)
for ic in invisible_clouds[:]:
ic.update()
if not ic.active:
invisible_clouds.remove(ic)
def check_collision():
bird_pos = (bird_x, bird_y)
for e in enemies:
if isinstance(e, Snake):
if bird_pos in e.get_all_positions():
return True
elif isinstance(e, Spider):
if bird_pos in e.get_positions():
return True
elif isinstance(e, Slime):
if e.get_pos() == bird_pos and e.slime_type not in ("light_gray_slime", "white_slime"):
return True
for fb in fireballs:
if not fb.active:
continue
if hasattr(fb, 'get_positions'):
if bird_pos in fb.get_positions():
return True
elif hasattr(fb, 'get_pos'):
if fb.get_pos() == bird_pos:
return True
for gc in gas_clouds:
if bird_pos in gc.get_affected_cells():
return True
return False
running = True
while running and current_state == "challenge2":
for event in pygame.event.get():
if event.type == pygame.QUIT:
current_state = "menu"
running = False
elif event.type == pygame.KEYDOWN and not state['game_over'] and not state['victory']:
if turn_state == 'player' and not player_moved_this_turn:
old_x, old_y = bird_x, bird_y
if event.key == pygame.K_w and bird_y > 0:
bird_y -= 1
last_direction = 'up'
elif event.key == pygame.K_s and bird_y < GRID_SIZE - 1:
bird_y += 1
last_direction = 'down'
elif event.key == pygame.K_a and bird_x > 0:
bird_x -= 1
last_direction = 'left'
elif event.key == pygame.K_d and bird_x < GRID_SIZE - 1:
bird_x += 1
last_direction = 'right'
if (bird_x, bird_y) != (old_x, old_y):
player_moved_this_turn = True
if check_collision():
state['game_over'] = True
turn_state = 'enemies'
elif event.type == pygame.KEYDOWN and (state['game_over'] or state['victory']):
if event.key == pygame.K_r:
show_challenge2()
return
elif event.key == pygame.K_q:
current_state = "menu"
running = False
if not state['game_over'] and not state['victory'] and turn_state == 'enemies':
update_enemies()
spawn_torch_fireballs()
spawn_enemy_if_needed() # <-- новый спавн
if check_collision():
state['game_over'] = True
turn_state = 'player'
player_moved_this_turn = False
turn_count += 1
if turn_count >= max_turns:
state['victory'] = True
# Отрисовка (без изменений)
screen.fill(WHITE)
draw_grid()
for e in enemies:
if isinstance(e, Snake):
e.draw(screen)
elif isinstance(e, Spider):
e.draw(screen)
elif isinstance(e, Slime):
e.draw(screen)
elif isinstance(e, Shadow):
e.draw(screen)
for fb in fireballs:
fb.draw(screen)
for gc in gas_clouds:
gc.draw(screen)
for ic in invisible_clouds:
ic.draw(screen)
draw_bird(screen, bird_x, bird_y, True)
turns_text = small_font.render(f"РҐРѕРґ: {turn_count} / {max_turns}", True, BLACK)
screen.blit(turns_text, (WIDTH - 200, 10))
if state['game_over']:
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [font.render("ПОРАЖЕНРР•!", True, RED_SNAKE),
small_font.render("R - рестарт | Q - выход", True, GREEN)]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
elif state['victory']:
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [font.render("ПОБЕДА!", True, GREEN),
small_font.render("R - рестарт | Q - выход", True, GREEN)]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
pygame.display.flip()
clock.tick(FPS)
def show_challenge3():
global current_state
bird_x, bird_y = GRID_SIZE // 2, GRID_SIZE // 2
last_direction = 'up'
state = {'apples_collected': 0, 'apple_pos': None, 'victory': False, 'game_over': False}
enemies = []
fireballs = []
gas_clouds = []
invisible_clouds = []
spider_fireballs = []
slime_balls = []
slime_gas_clouds = []
turn_count = 0
turn_state = 'player'
player_moved_this_turn = False
MAX_ENEMIES = 10
SPAWN_CHANCE = 0.35
def spawn_apple():
occupied = set()
for e in enemies:
if isinstance(e, Snake):
occupied.update(e.get_all_positions())
elif isinstance(e, Spider):
occupied.update(e.get_positions())
elif isinstance(e, Slime):
occupied.add(e.get_pos())
elif isinstance(e, Shadow):
for i in range(e.size):
for j in range(e.size):
occupied.add((e.x + i, e.y + j))
free = [(x, y) for x in range(GRID_SIZE) for y in range(GRID_SIZE)
if (x, y) not in occupied and (x, y) != (bird_x, bird_y)]
if free:
state['apple_pos'] = random.choice(free)
else:
state['apple_pos'] = None
# Начальные враги: быстрые зелёная и синяя змеи, стреляющие зелёная и синяя, коричневый паук, жёлтый слизень, чёрная змея
enemies.append(create_snake_at_edge("bright_green"))
enemies.append(create_snake_at_edge("light_blue"))
enemies.append(create_snake_at_edge("stripe_green"))
enemies.append(create_snake_at_edge("stripe_blue"))
sh = Shadow(random.randint(0, GRID_SIZE-1), random.randint(0, GRID_SIZE-1), 1, 3, "brown_gray")
enemies.append(sh)
enemies.append(create_snake_at_edge("black"))
spawn_apple()
def spawn_enemy_if_needed():
if len(enemies) >= MAX_ENEMIES:
return
if random.random() > SPAWN_CHANCE:
return
available_types = [
"bright_green", "light_blue", "stripe_green", "stripe_blue",
"brown_gray", "black"
]
enemy_type = random.choice(available_types)
if enemy_type in ("bright_green", "light_blue", "stripe_green", "stripe_blue", "black"):
new_snake = create_snake_at_edge(enemy_type)
collision = False
for e in enemies:
if isinstance(e, Snake):
if new_snake.get_all_positions() & e.get_all_positions():
collision = True
break
elif isinstance(e, Spider):
if new_snake.get_all_positions() & e.get_positions():
collision = True
break
elif isinstance(e, Slime):
if any(pos == e.get_pos() for pos in new_snake.get_all_positions()):
collision = True
break
if not collision:
enemies.append(new_snake)
elif enemy_type == "brown_gray":
size = 1
max_attempts = 20
for _ in range(max_attempts):
x = random.randint(0, GRID_SIZE - size)
y = random.randint(0, GRID_SIZE - size)
occupied = set()
for e in enemies:
if isinstance(e, Spider):
occupied.update(e.get_positions())
elif isinstance(e, Shadow):
for i in range(e.size):
for j in range(e.size):
occupied.add((e.x + i, e.y + j))
collision = any((x + i, y + j) in occupied for i in range(size) for j in range(size))
if not collision:
enemies.append(Shadow(x, y, size, 3, enemy_type))
break
else: # yellow_slime
new_slime = spawn_slime(enemy_type)
collision = any(isinstance(e, Slime) and e.get_pos() == new_slime.get_pos() for e in enemies)
if not collision:
enemies.append(new_slime)
def update_enemies():
snakes = [e for e in enemies if isinstance(e, Snake)]
spiders = [e for e in enemies if isinstance(e, Spider)]
slimes = [e for e in enemies if isinstance(e, Slime)]
shadows = [e for e in enemies if isinstance(e, Shadow)]
for sh in shadows[:]:
if sh.update():
spider = Spider(sh.x, sh.y, sh.spider_type)
enemies.append(spider)
enemies.remove(sh)
for snake in snakes:
snake.move(bird_x, bird_y, fireballs, invisible_clouds)
if snake.is_completely_off_grid():
enemies.remove(snake)
for spider in spiders:
spider.update(spider_fireballs, gas_clouds, bird_x, bird_y)
if not spider.active:
enemies.remove(spider)
new_slimes = []
for slime in slimes[:]:
slime.update(slime_balls, slime_gas_clouds, invisible_clouds, new_slimes)
if not slime.active:
enemies.remove(slime)
enemies.extend(new_slimes)
# Обновление всех снарядов
for fb_list in [fireballs, spider_fireballs, slime_balls]:
for fb in fb_list[:]:
if hasattr(fb, 'move'):
fb.move()
elif hasattr(fb, 'update'):
fb.update()
if not fb.active:
fb_list.remove(fb)
for gc_list in [gas_clouds, slime_gas_clouds]:
for gc in gc_list[:]:
gc.update()
if not gc.active:
gc_list.remove(gc)
for ic in invisible_clouds[:]:
ic.update()
if not ic.active:
invisible_clouds.remove(ic)
if state['apple_pos'] and (bird_x, bird_y) == state['apple_pos']:
state['apples_collected'] += 1
if state['apples_collected'] >= 17:
state['victory'] = True
else:
spawn_apple()
def check_collision():
bird_pos = (bird_x, bird_y)
for e in enemies:
if isinstance(e, Snake):
if bird_pos in e.get_all_positions():
return True
elif isinstance(e, Spider):
if bird_pos in e.get_positions():
return True
elif isinstance(e, Slime):
if e.get_pos() == bird_pos and e.slime_type not in ("light_gray_slime", "white_slime"):
return True
for fb_list in [fireballs, spider_fireballs, slime_balls]:
for fb in fb_list:
if not fb.active:
continue
if hasattr(fb, 'get_positions'):
if bird_pos in fb.get_positions():
return True
elif hasattr(fb, 'get_pos'):
if fb.get_pos() == bird_pos:
return True
for gc_list in [gas_clouds, slime_gas_clouds]:
for gc in gc_list:
if bird_pos in gc.get_affected_cells():
return True
return False
running = True
while running and current_state == "challenge3":
for event in pygame.event.get():
if event.type == pygame.QUIT:
current_state = "menu"
running = False
elif event.type == pygame.KEYDOWN and not state['game_over'] and not state['victory']:
if turn_state == 'player' and not player_moved_this_turn:
old_x, old_y = bird_x, bird_y
if event.key == pygame.K_w and bird_y > 0:
bird_y -= 1
last_direction = 'up'
elif event.key == pygame.K_s and bird_y < GRID_SIZE - 1:
bird_y += 1
last_direction = 'down'
elif event.key == pygame.K_a and bird_x > 0:
bird_x -= 1
last_direction = 'left'
elif event.key == pygame.K_d and bird_x < GRID_SIZE - 1:
bird_x += 1
last_direction = 'right'
if (bird_x, bird_y) != (old_x, old_y):
player_moved_this_turn = True
if check_collision():
state['game_over'] = True
else:
if state['apple_pos'] and (bird_x, bird_y) == state['apple_pos']:
state['apples_collected'] += 1
if state['apples_collected'] >= 17:
state['victory'] = True
else:
spawn_apple()
turn_state = 'enemies'
elif event.type == pygame.KEYDOWN and (state['game_over'] or state['victory']):
if event.key == pygame.K_r:
show_challenge3()
return
elif event.key == pygame.K_q:
current_state = "menu"
running = False
if not state['game_over'] and not state['victory'] and turn_state == 'enemies':
update_enemies()
spawn_enemy_if_needed()
if check_collision():
state['game_over'] = True
turn_state = 'player'
player_moved_this_turn = False
turn_count += 1
# Отрисовка (аналогично challenge1, но используем стиль по умолчанию)
screen.fill(WHITE)
draw_grid()
for e in enemies:
if isinstance(e, Snake):
e.draw(screen)
elif isinstance(e, Spider):
e.draw(screen)
elif isinstance(e, Slime):
e.draw(screen)
elif isinstance(e, Shadow):
e.draw(screen)
for fb in fireballs + spider_fireballs + slime_balls:
fb.draw(screen)
for gc in gas_clouds + slime_gas_clouds:
gc.draw(screen)
for ic in invisible_clouds:
ic.draw(screen)
if state['apple_pos']:
ax, ay = state['apple_pos']
rect = pygame.Rect(ax * CELL_SIZE, ay * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.circle(screen, (255, 0, 0), rect.center, CELL_SIZE // 3)
draw_bird(screen, bird_x, bird_y, True)
apple_text = small_font.render(f"Яблок: {state['apples_collected']} / 17", True, BLACK)
screen.blit(apple_text, (WIDTH - 200, 10))
if state['game_over']:
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [font.render("ПОРАЖЕНРР•!", True, RED_SNAKE),
small_font.render("R - рестарт | Q - выход", True, GREEN)]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
elif state['victory']:
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [font.render("ПОБЕДА!", True, GREEN),
small_font.render("R - рестарт | Q - выход", True, GREEN)]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
pygame.display.flip()
clock.tick(FPS)
def show_challenge4():
global current_state
bird_x, bird_y = GRID_SIZE // 2, GRID_SIZE // 2
last_direction = 'up'
state = {'killed': 0, 'victory': False, 'game_over': False}
enemies = []
fireballs = []
gas_clouds = []
invisible_clouds = []
spider_fireballs = []
slime_balls = []
slime_gas_clouds = []
turn_count = 0
turn_state = 'player'
player_moved_this_turn = False
MAX_ENEMIES = 4
SPAWN_CHANCE = 0.5
# Начальные враги: по одному радужному каждого вида
enemies.append(create_snake_at_edge("rainbow_snake"))
sh_spider = Shadow(random.randint(0, GRID_SIZE-1), random.randint(0, GRID_SIZE-1), 1, 3, "rainbow_spider")
enemies.append(sh_spider)
enemies.append(spawn_slime("rainbow_slime"))
def spawn_enemy_if_needed():
if len(enemies) >= MAX_ENEMIES:
return
if random.random() > SPAWN_CHANCE:
return
enemy_type = random.choice(["rainbow_snake", "rainbow_spider", "rainbow_slime"])
if enemy_type == "rainbow_snake":
new_snake = create_snake_at_edge("rainbow_snake")
collision = any(isinstance(e, Snake) and new_snake.get_all_positions() & e.get_all_positions() for e in enemies)
if not collision:
enemies.append(new_snake)
elif enemy_type == "rainbow_spider":
size = 1
max_attempts = 20
for _ in range(max_attempts):
x = random.randint(0, GRID_SIZE - size)
y = random.randint(0, GRID_SIZE - size)
occupied = set()
for e in enemies:
if isinstance(e, Spider):
occupied.update(e.get_positions())
elif isinstance(e, Shadow):
for i in range(e.size):
for j in range(e.size):
occupied.add((e.x + i, e.y + j))
collision = any((x + i, y + j) in occupied for i in range(size) for j in range(size))
if not collision:
enemies.append(Shadow(x, y, size, 3, enemy_type))
break
else: # rainbow_slime
new_slime = spawn_slime("rainbow_slime")
collision = any(isinstance(e, Slime) and e.get_pos() == new_slime.get_pos() for e in enemies)
if not collision:
enemies.append(new_slime)
def update_enemies():
nonlocal state
snakes = [e for e in enemies if isinstance(e, Snake)]
spiders = [e for e in enemies if isinstance(e, Spider)]
slimes = [e for e in enemies if isinstance(e, Slime)]
shadows = [e for e in enemies if isinstance(e, Shadow)]
for sh in shadows[:]:
if sh.update():
spider = Spider(sh.x, sh.y, sh.spider_type)
enemies.append(spider)
enemies.remove(sh)
for snake in snakes:
snake.move(bird_x, bird_y, fireballs, invisible_clouds)
if snake.is_completely_off_grid():
enemies.remove(snake)
state['killed'] += 1
for spider in spiders:
spider.update(spider_fireballs, gas_clouds, bird_x, bird_y)
if not spider.active:
enemies.remove(spider)
state['killed'] += 1
new_slimes = []
for slime in slimes[:]:
slime.update(slime_balls, slime_gas_clouds, invisible_clouds, new_slimes)
if not slime.active:
enemies.remove(slime)
state['killed'] += 1
enemies.extend(new_slimes)
for fb_list in [fireballs, spider_fireballs, slime_balls]:
for fb in fb_list[:]:
if hasattr(fb, 'move'):
fb.move()
elif hasattr(fb, 'update'):
fb.update()
if not fb.active:
fb_list.remove(fb)
for gc_list in [gas_clouds, slime_gas_clouds]:
for gc in gc_list[:]:
gc.update()
if not gc.active:
gc_list.remove(gc)
for ic in invisible_clouds[:]:
ic.update()
if not ic.active:
invisible_clouds.remove(ic)
if state['killed'] >= 20:
state['victory'] = True
def check_collision():
bird_pos = (bird_x, bird_y)
for e in enemies:
if isinstance(e, Snake):
if bird_pos in e.get_all_positions():
return True
elif isinstance(e, Spider):
if bird_pos in e.get_positions():
return True
elif isinstance(e, Slime):
if e.get_pos() == bird_pos and e.slime_type not in ("light_gray_slime", "white_slime"):
return True
for fb_list in [fireballs, spider_fireballs, slime_balls]:
for fb in fb_list:
if not fb.active:
continue
if hasattr(fb, 'get_positions'):
if bird_pos in fb.get_positions():
return True
elif hasattr(fb, 'get_pos'):
if fb.get_pos() == bird_pos:
return True
for gc_list in [gas_clouds, slime_gas_clouds]:
for gc in gc_list:
if bird_pos in gc.get_affected_cells():
return True
return False
running = True
while running and current_state == "challenge4":
for event in pygame.event.get():
if event.type == pygame.QUIT:
current_state = "menu"
running = False
elif event.type == pygame.KEYDOWN and not state['game_over'] and not state['victory']:
if turn_state == 'player' and not player_moved_this_turn:
old_x, old_y = bird_x, bird_y
if event.key == pygame.K_w and bird_y > 0:
bird_y -= 1
last_direction = 'up'
elif event.key == pygame.K_s and bird_y < GRID_SIZE - 1:
bird_y += 1
last_direction = 'down'
elif event.key == pygame.K_a and bird_x > 0:
bird_x -= 1
last_direction = 'left'
elif event.key == pygame.K_d and bird_x < GRID_SIZE - 1:
bird_x += 1
last_direction = 'right'
if (bird_x, bird_y) != (old_x, old_y):
player_moved_this_turn = True
if check_collision():
state['game_over'] = True
turn_state = 'enemies'
elif event.type == pygame.KEYDOWN and (state['game_over'] or state['victory']):
if event.key == pygame.K_r:
show_challenge4()
return
elif event.key == pygame.K_q:
current_state = "menu"
running = False
if not state['game_over'] and not state['victory'] and turn_state == 'enemies':
update_enemies()
spawn_enemy_if_needed()
if check_collision():
state['game_over'] = True
turn_state = 'player'
player_moved_this_turn = False
turn_count += 1
screen.fill(WHITE)
draw_grid()
for e in enemies:
if isinstance(e, Snake):
e.draw(screen)
elif isinstance(e, Spider):
e.draw(screen)
elif isinstance(e, Slime):
e.draw(screen)
elif isinstance(e, Shadow):
e.draw(screen)
for fb in fireballs + spider_fireballs + slime_balls:
fb.draw(screen)
for gc in gas_clouds + slime_gas_clouds:
gc.draw(screen)
for ic in invisible_clouds:
ic.draw(screen)
draw_bird(screen, bird_x, bird_y, True)
kills_text = small_font.render(f"Убито: {state['killed']} / 20", True, BLACK)
screen.blit(kills_text, (WIDTH - 200, 10))
if state['game_over']:
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [font.render("ПОРАЖЕНРР•!", True, RED_SNAKE),
small_font.render("R - рестарт | Q - выход", True, GREEN)]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
elif state['victory']:
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [font.render("ПОБЕДА!", True, GREEN),
small_font.render("R - рестарт | Q - выход", True, GREEN)]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
pygame.display.flip()
clock.tick(FPS)
def show_challenge5():
global current_state
bird_x, bird_y = GRID_SIZE // 2, GRID_SIZE // 2
last_direction = 'up'
state = {'apples_collected': 0, 'apple_pos': None, 'victory': False, 'game_over': False}
enemies = []
fireballs = []
gas_clouds = []
invisible_clouds = []
spider_fireballs = []
slime_balls = []
slime_gas_clouds = []
lava_cells_temp = []
turn_count = 0
turn_state = 'player'
player_moved_this_turn = False
MAX_ENEMIES = 10
SPAWN_CHANCE = 0.35
def spawn_apple():
occupied = set()
for e in enemies:
if isinstance(e, Snake):
occupied.update(e.get_all_positions())
elif isinstance(e, Spider):
occupied.update(e.get_positions())
elif isinstance(e, Slime):
occupied.add(e.get_pos())
elif isinstance(e, Shadow):
for i in range(e.size):
for j in range(e.size):
occupied.add((e.x + i, e.y + j))
free = [(x, y) for x in range(GRID_SIZE) for y in range(GRID_SIZE)
if (x, y) not in occupied and (x, y) != (bird_x, bird_y)]
if free:
state['apple_pos'] = random.choice(free)
else:
state['apple_pos'] = None
# Начальные враги
enemies.append(create_snake_at_edge("dark_blue"))
enemies.append(create_snake_at_edge("white_red"))
enemies.append(create_snake_at_edge("pink_azure"))
enemies.append(spawn_slime("bright_orange_slime", lava_cells_list=lava_cells_temp))
sh = Shadow(random.randint(0, GRID_SIZE-2), random.randint(0, GRID_SIZE-2), 2, 5, "dark_red")
enemies.append(sh)
spawn_apple()
def spawn_enemy_if_needed():
if len(enemies) >= MAX_ENEMIES:
return
if random.random() > SPAWN_CHANCE:
return
available_types = ["dark_blue", "white_red", "pink_azure", "bright_orange_slime", "dark_red"]
enemy_type = random.choice(available_types)
if enemy_type in ("dark_blue", "white_red", "pink_azure"):
new_snake = create_snake_at_edge(enemy_type)
collision = False
for e in enemies:
if isinstance(e, Snake):
if new_snake.get_all_positions() & e.get_all_positions():
collision = True
break
elif isinstance(e, Spider):
if new_snake.get_all_positions() & e.get_positions():
collision = True
break
elif isinstance(e, Slime):
if any(pos == e.get_pos() for pos in new_snake.get_all_positions()):
collision = True
break
if not collision:
enemies.append(new_snake)
elif enemy_type == "bright_orange_slime":
new_slime = spawn_slime(enemy_type, lava_cells_list=lava_cells_temp)
collision = any(isinstance(e, Slime) and e.get_pos() == new_slime.get_pos() for e in enemies)
if not collision:
enemies.append(new_slime)
else: # dark_red
size = 2
max_attempts = 20
for _ in range(max_attempts):
x = random.randint(0, GRID_SIZE - size)
y = random.randint(0, GRID_SIZE - size)
occupied = set()
for e in enemies:
if isinstance(e, Spider):
occupied.update(e.get_positions())
elif isinstance(e, Shadow):
for i in range(e.size):
for j in range(e.size):
occupied.add((e.x + i, e.y + j))
collision = any((x + i, y + j) in occupied for i in range(size) for j in range(size))
if not collision:
enemies.append(Shadow(x, y, size, 5, enemy_type))
break
def update_enemies():
snakes = [e for e in enemies if isinstance(e, Snake)]
spiders = [e for e in enemies if isinstance(e, Spider)]
slimes = [e for e in enemies if isinstance(e, Slime)]
shadows = [e for e in enemies if isinstance(e, Shadow)]
for sh in shadows[:]:
if sh.update():
spider = Spider(sh.x, sh.y, sh.spider_type)
enemies.append(spider)
enemies.remove(sh)
for snake in snakes:
snake.move(bird_x, bird_y, fireballs, invisible_clouds)
if snake.is_completely_off_grid():
enemies.remove(snake)
for spider in spiders:
spider.update(spider_fireballs, gas_clouds, bird_x, bird_y)
if not spider.active:
enemies.remove(spider)
new_slimes = []
for slime in slimes[:]:
slime.update(slime_balls, slime_gas_clouds, invisible_clouds, new_slimes, lava_cells_temp)
if not slime.active:
enemies.remove(slime)
enemies.extend(new_slimes)
# Лава
for lava in lava_cells_temp[:]:
lava.update()
if not lava.active:
lava_cells_temp.remove(lava)
for fb_list in [fireballs, spider_fireballs, slime_balls]:
for fb in fb_list[:]:
if hasattr(fb, 'move'):
fb.move()
elif hasattr(fb, 'update'):
fb.update()
if not fb.active:
fb_list.remove(fb)
for gc_list in [gas_clouds, slime_gas_clouds]:
for gc in gc_list[:]:
gc.update()
if not gc.active:
gc_list.remove(gc)
for ic in invisible_clouds[:]:
ic.update()
if not ic.active:
invisible_clouds.remove(ic)
if state['apple_pos'] and (bird_x, bird_y) == state['apple_pos']:
state['apples_collected'] += 1
if state['apples_collected'] >= 25:
state['victory'] = True
else:
spawn_apple()
def check_collision():
bird_pos = (bird_x, bird_y)
for e in enemies:
if isinstance(e, Snake):
if bird_pos in e.get_all_positions():
return True
elif isinstance(e, Spider):
if bird_pos in e.get_positions():
return True
elif isinstance(e, Slime):
if e.get_pos() == bird_pos and e.slime_type not in ("light_gray_slime", "white_slime"):
return True
for fb_list in [fireballs, spider_fireballs, slime_balls]:
for fb in fb_list:
if not fb.active:
continue
if hasattr(fb, 'get_positions'):
if bird_pos in fb.get_positions():
return True
elif hasattr(fb, 'get_pos'):
if fb.get_pos() == bird_pos:
return True
for gc_list in [gas_clouds, slime_gas_clouds]:
for gc in gc_list:
if bird_pos in gc.get_affected_cells():
return True
for lava in lava_cells_temp:
if lava.active and lava.get_pos() == bird_pos:
return True
return False
running = True
while running and current_state == "challenge5":
for event in pygame.event.get():
if event.type == pygame.QUIT:
current_state = "menu"
running = False
elif event.type == pygame.KEYDOWN and not state['game_over'] and not state['victory']:
if turn_state == 'player' and not player_moved_this_turn:
old_x, old_y = bird_x, bird_y
if event.key == pygame.K_w and bird_y > 0:
bird_y -= 1
last_direction = 'up'
elif event.key == pygame.K_s and bird_y < GRID_SIZE - 1:
bird_y += 1
last_direction = 'down'
elif event.key == pygame.K_a and bird_x > 0:
bird_x -= 1
last_direction = 'left'
elif event.key == pygame.K_d and bird_x < GRID_SIZE - 1:
bird_x += 1
last_direction = 'right'
if (bird_x, bird_y) != (old_x, old_y):
player_moved_this_turn = True
if check_collision():
state['game_over'] = True
else:
if state['apple_pos'] and (bird_x, bird_y) == state['apple_pos']:
state['apples_collected'] += 1
if state['apples_collected'] >= 25:
state['victory'] = True
else:
spawn_apple()
turn_state = 'enemies'
elif event.type == pygame.KEYDOWN and (state['game_over'] or state['victory']):
if event.key == pygame.K_r:
show_challenge5()
return
elif event.key == pygame.K_q:
current_state = "menu"
running = False
if not state['game_over'] and not state['victory'] and turn_state == 'enemies':
update_enemies()
spawn_enemy_if_needed()
if check_collision():
state['game_over'] = True
turn_state = 'player'
player_moved_this_turn = False
turn_count += 1
screen.fill(WHITE)
draw_grid()
for lava in lava_cells_temp:
if lava.active:
lava.draw(screen)
for e in enemies:
if isinstance(e, Snake):
e.draw(screen)
elif isinstance(e, Spider):
e.draw(screen)
elif isinstance(e, Slime):
e.draw(screen)
elif isinstance(e, Shadow):
e.draw(screen)
for fb in fireballs + spider_fireballs + slime_balls:
fb.draw(screen)
for gc in gas_clouds + slime_gas_clouds:
gc.draw(screen)
for ic in invisible_clouds:
ic.draw(screen)
if state['apple_pos']:
ax, ay = state['apple_pos']
rect = pygame.Rect(ax * CELL_SIZE, ay * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.circle(screen, (255, 0, 0), rect.center, CELL_SIZE // 3)
draw_bird(screen, bird_x, bird_y, True)
apple_text = small_font.render(f"Яблок: {state['apples_collected']} / 25", True, BLACK)
screen.blit(apple_text, (WIDTH - 200, 10))
if state['game_over']:
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [font.render("ПОРАЖЕНРР•!", True, RED_SNAKE),
small_font.render("R - рестарт | Q - выход", True, GREEN)]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
elif state['victory']:
overlay = pygame.Surface((WIDTH, HEIGHT))
overlay.set_alpha(200)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
texts = [font.render("ПОБЕДА!", True, GREEN),
small_font.render("R - рестарт | Q - выход", True, GREEN)]
y_text = HEIGHT // 2 - 40
for text in texts:
rect = text.get_rect(center=(WIDTH // 2, y_text))
screen.blit(text, rect)
y_text += 40
pygame.display.flip()
clock.tick(FPS)
def start_game_mode(mode):
global current_state, selected_abilities
selected_abilities = {}
current_state = "ability_selection"
global game_mode
game_mode = mode
show_ability_selection(mode)
game_mode = None
def show_ability_selection(mode):
global current_state, selected_abilities
max_slots = 5 if mode != "sandbox" else float('inf')
selected = {i: False for i in range(len(ABILITY_INFO))}
back_button = Button(WIDTH // 2 - 150, HEIGHT - 70, 300, 50, "Начать игру",
action=lambda: start_game_with_abilities())
def start_game_with_abilities():
nonlocal selected
global current_state, selected_abilities
chosen = {}
total_slots = 0
for idx, info in enumerate(ABILITY_INFO):
if selected[idx]:
total_slots += info["slots"]
if total_slots <= max_slots or max_slots == float('inf'):
for idx, info in enumerate(ABILITY_INFO):
if selected[idx]:
chosen[info["name"]] = {
"uses": info["uses"],
"cooldown": 0,
"slots": info["slots"],
"key": info["key"],
"max_uses": info["uses"],
"max_cooldown": info["cooldown"]
}
selected_abilities = chosen
if mode == "snakes":
current_state = "game"
elif mode == "spiders":
current_state = "spider_game"
elif mode == "slimes":
current_state = "slime_game"
elif mode == "insects":
current_state = "insect_game"
elif mode == "fish":
current_state = "fish_game"
elif mode == "sandbox":
current_state = "sandbox_play"
elif mode.startswith("level"): # <-- добавить
current_state = mode # "level1" или "level2"
else:
pass
while current_state == "ability_selection":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
mouse_x, mouse_y = event.pos
for idx, info in enumerate(ABILITY_INFO):
rect = pygame.Rect(WIDTH // 2 - 300, 150 + idx * 45, 30, 30)
if rect.collidepoint(mouse_x, mouse_y):
selected[idx] = not selected[idx]
back_button.handle_event(event)
screen.fill(MENU_BG)
title = font.render("Выберите способности", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 50))
total_slots = sum(ABILITY_INFO[idx]["slots"] for idx, sel in selected.items() if sel)
slot_text = f"Занято ячеек: {total_slots} / {max_slots if max_slots != float('inf') else '∞'}"
slot_surf = small_font.render(slot_text, True, BLACK if total_slots <= max_slots else RED_SNAKE)
screen.blit(slot_surf, (WIDTH // 2 - slot_surf.get_width() // 2, 100))
y = 150
for idx, info in enumerate(ABILITY_INFO):
row_rect = pygame.Rect(WIDTH // 2 - 320, y, 640, 40)
pygame.draw.rect(screen, (220, 220, 220), row_rect, border_radius=5)
cb_rect = pygame.Rect(WIDTH // 2 - 300, y + 5, 30, 30)
pygame.draw.rect(screen, BLACK, cb_rect, 2)
if selected[idx]:
pygame.draw.line(screen, BLACK, (cb_rect.left + 4, cb_rect.centery),
(cb_rect.centerx, cb_rect.bottom - 4), 4)
pygame.draw.line(screen, BLACK, (cb_rect.centerx, cb_rect.bottom - 4),
(cb_rect.right - 4, cb_rect.top + 4), 4)
name_surf = small_font.render(f"{info['name']} (клавиша {info['key']}) - {info['slots']} яч.", True, BLACK)
screen.blit(name_surf, (WIDTH // 2 - 250, y + 10))
desc_surf = micro_font.render(info["description"], True, (50, 50, 50))
screen.blit(desc_surf, (WIDTH // 2 - 250, y + 25))
y += 45
back_button.draw(screen)
pygame.display.flip()
clock.tick(FPS)
def show_controls():
global current_state
back_button = Button(WIDTH // 2 - 100, HEIGHT - 70, 200, 50, "В меню", action=lambda: go_to_menu())
while current_state == "controls":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
back_button.handle_event(event)
screen.fill(MENU_BG)
title = font.render("Управление", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 40))
lines = [
"Вы играете за птичку на поле 17x17.",
"Цель: уклоняться от врагов.",
"",
"Управление:",
"W, A, S, D - движение вверх, влево, вниз, вправо (пошагово).",
"1-7 - использование способностей.",
"R - перезапуск игры после поражения.",
"Q - выход в главное меню.",
"",
"Совет: изучайте врагов и способности в Бестиарии."
]
y = 130
for line in lines:
text = small_font.render(line, True, BLACK)
screen.blit(text, (50, y))
y += 30
back_button.draw(screen)
pygame.display.flip()
def show_skin_select():
global current_state, bird_skin
back_button = Button(WIDTH // 2 - 100, HEIGHT - 70, 200, 50, "Назад", action=go_to_menu)
buttons = [
Button(WIDTH // 2 - 350, 200, 120, 50, "Серый", action=lambda: set_skin(0)),
Button(WIDTH // 2 - 350, 300, 120, 50, "РЎРёРЅРёР№", action=lambda: set_skin(1)),
Button(WIDTH // 2 - 350, 400, 120, 50, "Красный", action=lambda: set_skin(2)),
Button(WIDTH // 2 - 350, 500, 120, 50, "Жёлтый", action=lambda: set_skin(3)),
Button(WIDTH // 2 - 350, 600, 120, 50, "Чёрный", action=lambda: set_skin(4)),
]
def set_skin(skin):
global bird_skin
bird_skin = skin
while current_state == "skin_select":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
back_button.handle_event(event)
for btn in buttons:
btn.handle_event(event)
screen.fill(MENU_BG)
title = font.render("Выберите скин птички", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 100))
preview_x = WIDTH // 2
preview_y = 280
draw_bird(screen, preview_x // CELL_SIZE, preview_y // CELL_SIZE, True)
for btn in buttons:
btn.draw(screen)
back_button.draw(screen)
pygame.display.flip()
def show_settings():
global current_state, fullscreen, screen, WIDTH, HEIGHT, CELL_SIZE, survival_records
def reset_records():
global survival_records
survival_records = {"snakes": 0, "spiders": 0, "slimes": 0, "insects": 0, "fish": 0}
save_records(survival_records)
back_button = Button(WIDTH // 2 - 250, HEIGHT - 70, 500, 50, "Назад", action=lambda: go_to_menu())
toggle_button = Button(WIDTH // 2 - 250, 200, 500, 50,
"Оконный режим (пока не доработано)" if fullscreen else "Полный экран (пока не доработано)",
action=toggle_fullscreen)
reset_records_button = Button(WIDTH // 2 - 250, 270, 500, 50, "Сбросить рекорды выживания",
action=reset_records)
while current_state == "settings":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
back_button.handle_event(event)
toggle_button.handle_event(event)
reset_records_button.handle_event(event)
screen.fill(MENU_BG)
title = font.render("Настройки", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 80))
toggle_button.draw(screen)
reset_records_button.draw(screen)
back_button.draw(screen)
pygame.display.flip()
def toggle_fullscreen():
global fullscreen, screen, WIDTH, HEIGHT, CELL_SIZE
fullscreen = not fullscreen
if fullscreen:
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
info = pygame.display.Info()
WIDTH, HEIGHT = info.current_w, info.current_h
CELL_SIZE = min(WIDTH, HEIGHT) // GRID_SIZE
WIDTH = GRID_SIZE * CELL_SIZE
HEIGHT = GRID_SIZE * CELL_SIZE
screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.FULLSCREEN)
else:
CELL_SIZE = 45
WIDTH = GRID_SIZE * CELL_SIZE
HEIGHT = GRID_SIZE * CELL_SIZE
screen = pygame.display.set_mode((WIDTH, HEIGHT))
def show_bestiary():
global current_state
bestiary_tab = 0 # 0 - змеи, 1 - пауки, 2 - слизни, 3 - насекомые, 4 - рыбы, 5 - другое
back_button = Button(WIDTH // 2 - 300, HEIGHT - 750, 600, 50, "В меню", action=lambda: go_to_menu())
snake_btn = Button(WIDTH // 2 - 375, 70, 120, 40, "Змеи", action=lambda: set_tab(0), color=(100, 200, 100),
hover_color=(50, 150, 50), font=small_font)
spider_btn = Button(WIDTH // 2 - 248, 70, 120, 40, "Пауки", action=lambda: set_tab(1), color=(100, 100, 200),
hover_color=(50, 50, 150), font=small_font)
slime_btn = Button(WIDTH // 2 - 123, 70, 120, 40, "Слизни", action=lambda: set_tab(2), color=(200, 200, 100),
hover_color=(150, 150, 50), font=small_font)
insect_btn = Button(WIDTH // 2 + 3, 70, 120, 40, "Насекомые", action=lambda: set_tab(3), color=(139, 69, 19),
hover_color=(101, 67, 33), font=small_font)
fish_btn = Button(WIDTH // 2 + 128, 70, 120, 40, "Рыбы", action=lambda: set_tab(4), color=(100, 200, 255),
hover_color=(50, 150, 200), font=small_font)
other_btn = Button(WIDTH // 2 + 255, 70, 120, 40, "Другое", action=lambda: set_tab(5), color=(200, 150, 200),
hover_color=(150, 100, 150), font=small_font)
def set_tab(idx):
nonlocal bestiary_tab
bestiary_tab = idx
current_snake_tab = 0
tab_buttons_snakes = []
tab_width = 130
tab_height = 40
tab_gap = 15
n_tabs_snakes = len(TAB_NAMES_SNAKES)
total_width_snakes = n_tabs_snakes * tab_width + (n_tabs_snakes - 1) * tab_gap
tab_start_x_snakes = (WIDTH - total_width_snakes) // 2
for i, name in enumerate(TAB_NAMES_SNAKES):
if name == "Зелёные":
color = (100, 200, 100)
hover = (50, 150, 50)
elif name == "РЎРёРЅРёРµ":
color = (100, 100, 200)
hover = (50, 50, 150)
elif name == "Белые":
color = (200, 200, 200)
hover = (150, 150, 150)
elif name == "РњРёРЅРёР±РѕСЃСЃС‹":
color = (200, 100, 100)
hover = (150, 50, 50)
else:
color = (200, 150, 200)
hover = (150, 100, 150)
x = tab_start_x_snakes + i * (tab_width + tab_gap)
btn = Button(x, 120, tab_width, tab_height, name,
action=lambda idx=i: set_snake_tab(idx),
color=color, hover_color=hover, text_color=BLACK, font=small_font)
tab_buttons_snakes.append(btn)
current_spider_tab = 0
tab_buttons_spiders = []
n_tabs_spiders = len(TAB_NAMES_SPIDERS)
total_width_spiders = n_tabs_spiders * tab_width + (n_tabs_spiders - 1) * tab_gap
tab_start_x_spiders = (WIDTH - total_width_spiders) // 2
for i, name in enumerate(TAB_NAMES_SPIDERS):
color = (150, 150, 150)
hover = (100, 100, 100)
if name == "Радужные":
color = (200, 150, 200)
hover = (150, 100, 150)
x = tab_start_x_spiders + i * (tab_width + tab_gap)
btn = Button(x, 120, tab_width, tab_height, name,
action=lambda idx=i: set_spider_tab(idx),
color=color, hover_color=hover, text_color=BLACK, font=small_font)
tab_buttons_spiders.append(btn)
current_slime_tab = 0
tab_buttons_slimes = []
n_tabs_slimes = len(TAB_NAMES_SLIMES)
total_width_slimes = n_tabs_slimes * tab_width + (n_tabs_slimes - 1) * tab_gap
tab_start_x_slimes = (WIDTH - total_width_slimes) // 2
for i, name in enumerate(TAB_NAMES_SLIMES):
color = (200, 200, 100)
hover = (150, 150, 50)
if name == "Чёрные слизни":
color = (100, 100, 100)
hover = (70, 70, 70)
elif name == "Радужные":
color = (200, 150, 200)
hover = (150, 100, 150)
x = tab_start_x_slimes + i * (tab_width + tab_gap)
btn = Button(x, 120, tab_width, tab_height, name,
action=lambda idx=i: set_slime_tab(idx),
color=color, hover_color=hover, text_color=BLACK, font=small_font)
tab_buttons_slimes.append(btn)
current_insect_tab = 0
tab_buttons_insects = []
n_tabs_insects = len(TAB_NAMES_INSECTS)
total_width_insects = n_tabs_insects * tab_width + (n_tabs_insects - 1) * tab_gap
tab_start_x_insects = (WIDTH - total_width_insects) // 2
for i, name in enumerate(TAB_NAMES_INSECTS):
color = (139, 69, 19)
hover = (101, 67, 33)
x = tab_start_x_insects + i * (tab_width + tab_gap)
btn = Button(x, 120, tab_width, tab_height, name,
action=lambda idx=i: set_insect_tab(idx),
color=color, hover_color=hover, text_color=BLACK, font=small_font)
tab_buttons_insects.append(btn)
current_fish_tab = 0
tab_buttons_fish = []
fish_tab_names = ["Рыбы"]
n_tabs_fish = len(fish_tab_names)
total_width_fish = n_tabs_fish * tab_width + (n_tabs_fish - 1) * tab_gap
tab_start_x_fish = (WIDTH - total_width_fish) // 2
for i, name in enumerate(fish_tab_names):
color = (100, 200, 255)
hover = (50, 150, 200)
x = tab_start_x_fish + i * (tab_width + tab_gap)
btn = Button(x, 120, tab_width, tab_height, name,
action=lambda idx=i: set_fish_tab(idx),
color=color, hover_color=hover, text_color=BLACK, font=small_font)
tab_buttons_fish.append(btn)
current_other_tab = 0
tab_buttons_other = []
other_tab_names = ["Газы", "Снаряды", "Способности", "Усложнения"]
n_tabs_other = len(other_tab_names)
total_width_other = n_tabs_other * tab_width + (n_tabs_other - 1) * tab_gap
tab_start_x_other = (WIDTH - total_width_other) // 2
for i, name in enumerate(other_tab_names):
color = (200, 150, 200)
hover = (150, 100, 150)
x = tab_start_x_other + i * (tab_width + tab_gap)
btn = Button(x, 120, tab_width, tab_height, name,
action=lambda idx=i: set_other_tab(idx),
color=color, hover_color=hover, text_color=BLACK, font=small_font)
tab_buttons_other.append(btn)
def set_snake_tab(idx):
nonlocal current_snake_tab
current_snake_tab = idx
def set_spider_tab(idx):
nonlocal current_spider_tab
current_spider_tab = idx
def set_slime_tab(idx):
nonlocal current_slime_tab
current_slime_tab = idx
def set_insect_tab(idx):
nonlocal current_insect_tab
current_insect_tab = idx
def set_fish_tab(idx):
nonlocal current_fish_tab
current_fish_tab = idx
def set_other_tab(idx):
nonlocal current_other_tab
current_other_tab = idx
GASES_INFO = [
{"name": "Ядовитое облако", "color": (100, 200, 100),
"description": "Наносит урон при касании. Радиус и длительность зависят от источника."},
{"name": "Невидимое облако", "color": (150, 150, 150),
"description": "Скрывает врагов и снаряды внутри. Не наносит урона."},
{"name": "Лава", "color": (255, 69, 0),
"description": "Наносит урон при касании. Длительность зависит от источника."},
]
PROJECTILES_INFO = [
{"name": "Зелёный шар", "color": (100, 255, 100), "description": "Медленный шар слизней (скорость 0.5)."},
{"name": "Оранжевый шар", "color": (255, 69, 0), "description": "Обычный огненный шар змей (скорость 1)."},
{"name": "Фиолетовый шар", "color": PURPLE_FIRE,
"description": "Быстрый шар (скорость 2), часто выпускается линиями."}
]
while current_state == "bestiary":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
back_button.handle_event(event)
snake_btn.handle_event(event)
spider_btn.handle_event(event)
slime_btn.handle_event(event)
insect_btn.handle_event(event)
fish_btn.handle_event(event)
other_btn.handle_event(event)
if bestiary_tab == 0:
for btn in tab_buttons_snakes:
btn.handle_event(event)
elif bestiary_tab == 1:
for btn in tab_buttons_spiders:
btn.handle_event(event)
elif bestiary_tab == 2:
for btn in tab_buttons_slimes:
btn.handle_event(event)
elif bestiary_tab == 3:
for btn in tab_buttons_insects:
btn.handle_event(event)
elif bestiary_tab == 4:
for btn in tab_buttons_fish:
btn.handle_event(event)
else:
for btn in tab_buttons_other:
btn.handle_event(event)
screen.fill(MENU_BG)
snake_btn.draw(screen)
spider_btn.draw(screen)
slime_btn.draw(screen)
insect_btn.draw(screen)
fish_btn.draw(screen)
other_btn.draw(screen)
if bestiary_tab == 0:
for btn in tab_buttons_snakes:
btn.draw(screen)
start_x = 20
start_y = 300
row_height = 50
col_widths = [130, 70, 80, 170, 170, 80]
headers = ["Тип", "Длина", "Скорость", "Особенности", "Вид"]
header_bg = (70, 70, 70)
header_text_color = (255, 255, 255)
category = TAB_NAMES_SNAKES[current_snake_tab]
snake_types = TAB_CATEGORIES_SNAKES[category]
snake_types = [st for st in SNAKE_INFO_ORDERED if st in snake_types]
table_width = sum(col_widths) + 40
table_height = row_height * (len(snake_types) + 1) + 20
table_x = start_x - 15
table_y = start_y - 40
pygame.draw.rect(screen, (50, 50, 50), (table_x + 5, table_y + 5, table_width, table_height),
border_radius=10)
pygame.draw.rect(screen, WHITE, (table_x, table_y, table_width, table_height), border_radius=10)
title = font.render(f"Бестиарий — {category}", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, start_y - 130))
desc_text = "Змеи длинные и выползают с краёв поля."
desc_surf = small_font.render(desc_text, True, BLACK)
screen.blit(desc_surf, (start_x, start_y - 80))
x = start_x
for i, header in enumerate(headers):
header_surf = micro_font.render(header, True, header_text_color)
pygame.draw.rect(screen, header_bg, (x - 5, start_y - 35, col_widths[i] + 10, row_height - 5))
screen.blit(header_surf, (x + 5, start_y - 30))
x += col_widths[i]
pygame.draw.line(screen, BLACK, (start_x - 5, start_y - 10), (x + 15, start_y - 10), 2)
y = start_y
for idx, snake_type in enumerate(snake_types):
info = SNAKE_INFO[snake_type]
if idx % 2 == 0:
row_color = (240, 248, 255)
else:
row_color = (255, 255, 240)
pygame.draw.rect(screen, row_color, (start_x - 5, y - 5, x - start_x + 25, row_height))
ru_names = {
"green": "Зелёная", "dark_green": "Тёмно-зелёная", "bright_green": "Ярко-зелёная",
"stripe_green": "Полосатая зелёная", "swamp_green": "Болотная",
"blue": "Синяя", "dark_blue": "Тёмно-синяя", "light_blue": "Светло-синяя",
"stripe_blue": "Полосатая синяя", "pink_azure": "Розово-лазурная",
"white": "Белая", "white_purple": "Бело-фиолетовая", "white_red": "Бело-красная",
"red": "Красная", "black": "Чёрная", "rainbow_snake": "Радужная", "white_gray": "Бело-серая"
}
type_surf = micro_font.render(ru_names.get(snake_type, snake_type), True, info.get("color", BLACK))
screen.blit(type_surf, (start_x + 5, y + 15))
len_surf = micro_font.render(str(info["length_range"]), True, BLACK)
screen.blit(len_surf, (start_x + col_widths[0] + 5, y + 15))
speed_surf = micro_font.render(str(info["speed"]), True, BLACK)
screen.blit(speed_surf, (start_x + col_widths[0] + col_widths[1] + 5, y + 15))
special_text = info["special"]
if len(special_text) > 25:
lines = [special_text[i:i + 20] for i in range(0, len(special_text), 20)]
for li, line in enumerate(lines[:2]):
special_surf = micro_font.render(line, True, BLACK)
screen.blit(special_surf,
(start_x + col_widths[0] + col_widths[1] + col_widths[2] + 5, y + 2 + li * 18))
else:
special_surf = micro_font.render(special_text, True, BLACK)
screen.blit(special_surf, (start_x + col_widths[0] + col_widths[1] + col_widths[2] + 5, y + 15))
icon_x = start_x + col_widths[0] + col_widths[1] + col_widths[2] + col_widths[3] + col_widths[4] + 15
icon_y = y + 17
head_color = info["head_color"]
body_color = info["body_color"]
if snake_type == "rainbow_snake":
hue = (pygame.time.get_ticks() // 10) % 360
c = pygame.Color(0)
c.hsva = (hue, 100, 100)
head_color = body_color = c
pygame.draw.rect(screen, head_color, (icon_x, icon_y, 16, 16))
pygame.draw.rect(screen, body_color, (icon_x + 16, icon_y, 16, 16))
pygame.draw.rect(screen, BLACK, (icon_x, icon_y, 32, 16), 1)
pygame.draw.circle(screen, WHITE, (icon_x + 4, icon_y + 4), 2)
pygame.draw.circle(screen, BLACK, (icon_x + 4, icon_y + 4), 1)
y += row_height
pygame.draw.line(screen, BLACK, (start_x - 5, y), (x + 15, y), 2)
elif bestiary_tab == 1:
for btn in tab_buttons_spiders:
btn.draw(screen)
start_x = 20
start_y = 300
row_height = 70
col_widths = [130, 50, 50, 50, 190, 70]
headers = ["Тип", "Разм", "Пад", "Сид", "Особенности", "Вид"]
header_bg = (70, 70, 70)
header_text_color = (255, 255, 255)
category = TAB_NAMES_SPIDERS[current_spider_tab]
spider_types = SPIDER_CATEGORIES[category]
spider_types = [st for st in SPIDER_INFO_ORDERED if st in spider_types]
table_width = sum(col_widths) + 40
table_height = row_height * (len(spider_types) + 1) + 20
table_x = start_x - 15
table_y = start_y - 40
pygame.draw.rect(screen, (50, 50, 50), (table_x + 5, table_y + 5, table_width, table_height),
border_radius=10)
pygame.draw.rect(screen, WHITE, (table_x, table_y, table_width, table_height), border_radius=10)
title = font.render(f"Бестиарий — Пауки ({category})", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, start_y - 130))
desc_text = "Пауки падают с неба с характерной тенью."
desc_surf = small_font.render(desc_text, True, BLACK)
screen.blit(desc_surf, (start_x, start_y - 80))
x = start_x
for i, header in enumerate(headers):
header_surf = micro_font.render(header, True, header_text_color)
pygame.draw.rect(screen, header_bg, (x - 5, start_y - 35, col_widths[i] + 10, row_height - 5))
screen.blit(header_surf, (x + 5, start_y - 30))
x += col_widths[i]
pygame.draw.line(screen, BLACK, (start_x - 5, start_y - 10), (x + 15, start_y - 10), 2)
y = start_y
for idx, spider_type in enumerate(spider_types):
info = SPIDER_INFO[spider_type]
if idx % 2 == 0:
row_color = (240, 248, 255)
else:
row_color = (255, 255, 240)
pygame.draw.rect(screen, row_color, (start_x - 5, y - 5, x - start_x + 25, row_height))
ru_names = {
"light_gray": "Светло-серый", "brown_gray": "Коричнево-серый",
"dark_gray_red_cross": "Тёмно-серый с крестом", "dark_gray_green_circle": "Тёмно-серый с кругом",
"dark_red": "Тёмно-красный", "big_dark_gray": "Большой тёмно-серый",
"big_dark_gray_green_circle": "Большой с зелёным кругом", "black_spider": "Чёрный",
"white_purple_circle": "Белый с фиолетовым", "rainbow_spider": "Радужный"
}
type_surf = micro_font.render(ru_names.get(spider_type, spider_type), True, info["color"])
screen.blit(type_surf, (start_x + 5, y + 8))
size_surf = micro_font.render(str(info["size"]), True, BLACK)
screen.blit(size_surf, (start_x + col_widths[0] + 5, y + 25))
fall_surf = micro_font.render(str(info["fall_time"]), True, BLACK)
screen.blit(fall_surf, (start_x + col_widths[0] + col_widths[1] + 5, y + 25))
sit_surf = micro_font.render(str(info["sit_time"]), True, BLACK)
screen.blit(sit_surf, (start_x + col_widths[0] + col_widths[1] + col_widths[2] + 5, y + 25))
special_text = info["special"]
words = special_text.split()
lines = []
current_line = ""
for w in words:
if len(current_line) + len(w) + 1 <= 25:
if current_line:
current_line += " "
current_line += w
else:
lines.append(current_line)
current_line = w
if current_line:
lines.append(current_line)
text_x = start_x + col_widths[0] + col_widths[1] + col_widths[2] + col_widths[3] + 5
for line_idx, line in enumerate(lines[:2]):
special_surf = micro_font.render(line, True, BLACK)
screen.blit(special_surf, (text_x, y + 8 + line_idx * 18))
icon_size = 32
icon_x = start_x + col_widths[0] + col_widths[1] + col_widths[2] + col_widths[3] + col_widths[4] + 10
icon_y = y + (row_height - icon_size) // 2
spider_icon_surface = pygame.Surface((icon_size, icon_size))
spider_icon_surface.fill(row_color)
body_color = info["color"]
if spider_type == "rainbow_spider":
hue = (pygame.time.get_ticks() // 10) % 360
c = pygame.Color(0)
c.hsva = (hue, 100, 100)
body_color = c
if info["size"] == 1:
pygame.draw.rect(spider_icon_surface, body_color, (4, 4, icon_size - 8, icon_size - 8))
pygame.draw.rect(spider_icon_surface, (0, 0, 0), (4, 4, icon_size - 8, icon_size - 8), 1)
eye_r = max(2, icon_size // 10)
pygame.draw.circle(spider_icon_surface, WHITE, (icon_size // 3, icon_size // 3), eye_r)
pygame.draw.circle(spider_icon_surface, WHITE, (2 * icon_size // 3, icon_size // 3), eye_r)
pygame.draw.circle(spider_icon_surface, BLACK, (icon_size // 3, icon_size // 3), eye_r // 2)
pygame.draw.circle(spider_icon_surface, BLACK, (2 * icon_size // 3, icon_size // 3), eye_r // 2)
leg_color = (0, 0, 0)
for ox, oy in [(-6, -6), (6, -6), (-6, 6), (6, 6), (-8, 0), (8, 0), (0, -8), (0, 8)]:
pygame.draw.line(spider_icon_surface, leg_color, (icon_size // 2, icon_size // 2),
(icon_size // 2 + ox // 2, icon_size // 2 + oy // 2), 1)
else:
pygame.draw.rect(spider_icon_surface, body_color, (2, 2, icon_size - 4, icon_size - 4))
pygame.draw.rect(spider_icon_surface, (0, 0, 0), (2, 2, icon_size - 4, icon_size - 4), 1)
if spider_type == "dark_gray_red_cross":
cx, cy = icon_size // 2, icon_size // 2
pygame.draw.line(spider_icon_surface, SPIDER_RED_CROSS, (cx - 8, cy), (cx + 8, cy), 2)
pygame.draw.line(spider_icon_surface, SPIDER_RED_CROSS, (cx, cy - 8), (cx, cy + 8), 2)
elif spider_type in ("dark_gray_green_circle", "big_dark_gray_green_circle"):
cx, cy = icon_size // 2, icon_size // 2
pygame.draw.circle(spider_icon_surface, SPIDER_GREEN_CIRCLE, (cx, cy), icon_size // 4)
pygame.draw.circle(spider_icon_surface, BLACK, (cx, cy), icon_size // 4, 1)
elif spider_type == "white_purple_circle":
cx, cy = icon_size // 2, icon_size // 2
pygame.draw.circle(spider_icon_surface, (128, 0, 128), (cx, cy), icon_size // 4)
elif spider_type == "black_spider":
eye_r = max(3, icon_size // 8)
pygame.draw.circle(spider_icon_surface, RED_EYE, (icon_size // 3, icon_size // 3), eye_r)
pygame.draw.circle(spider_icon_surface, RED_EYE, (2 * icon_size // 3, icon_size // 3), eye_r)
pygame.draw.circle(spider_icon_surface, BLACK, (icon_size // 3, icon_size // 3), eye_r // 2)
pygame.draw.circle(spider_icon_surface, BLACK, (2 * icon_size // 3, icon_size // 3), eye_r // 2)
screen.blit(spider_icon_surface, (icon_x, icon_y))
pygame.draw.rect(screen, BLACK, (icon_x, icon_y, icon_size, icon_size), 1)
y += row_height
pygame.draw.line(screen, BLACK, (start_x - 5, y), (x + 15, y), 2)
elif bestiary_tab == 2:
for btn in tab_buttons_slimes:
btn.draw(screen)
start_x = 20
start_y = 300
row_height = 80
col_widths = [160, 70, 80, 90, 160, 70]
headers = ["РўРёРї", "Прыжок", "Частота", "Макс.прыж.", "Рффект РїСЂРё смерти", "Р’РёРґ"]
header_bg = (70, 70, 70)
header_text_color = (255, 255, 255)
category = TAB_NAMES_SLIMES[current_slime_tab]
slime_types = SLIME_CATEGORIES[category]
slime_types = [st for st in SLIME_INFO_ORDERED if st in slime_types]
table_width = sum(col_widths) + 40
table_height = row_height * (len(slime_types) + 1) + 20
table_x = start_x - 15
table_y = start_y - 40
pygame.draw.rect(screen, (50, 50, 50), (table_x + 5, table_y + 5, table_width, table_height),
border_radius=10)
pygame.draw.rect(screen, WHITE, (table_x, table_y, table_width, table_height), border_radius=10)
title = font.render(f"Бестиарий — Слизни ({category})", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, start_y - 130))
desc_text = "Слизни перепрыгивают клетки и разрушаются после определённого количества прыжков."
desc_surf = small_font.render(desc_text, True, BLACK)
screen.blit(desc_surf, (start_x, start_y - 80))
x = start_x
for i, header in enumerate(headers):
header_surf = micro_font.render(header, True, header_text_color)
pygame.draw.rect(screen, header_bg, (x - 5, start_y - 35, col_widths[i] + 10, row_height - 5))
screen.blit(header_surf, (x + 5, start_y - 30))
x += col_widths[i]
pygame.draw.line(screen, BLACK, (start_x - 5, start_y - 10), (x + 15, start_y - 10), 2)
y = start_y
for idx, slime_type in enumerate(slime_types):
info = SLIME_INFO[slime_type]
if idx % 2 == 0:
row_color = (240, 248, 255)
else:
row_color = (255, 255, 240)
pygame.draw.rect(screen, row_color, (start_x - 5, y - 5, x - start_x + 25, row_height))
ru_names = {
"green_slime_jump1": "Зелёный прыгун 1", "green_slime_jump2": "Зелёный прыгун 2",
"dark_green_slime": "Тёмно-зелёный", "red_slime": "Красный",
"purple_blue_slime": "Фиолетово-синий",
"black_slime": "Чёрный", "dark_gray_slime": "Тёмно-серый", "light_gray_slime": "Светло-серый",
"white_slime": "Белый", "rainbow_slime": "Радужный",
"bright_orange_slime": "Ярко-оранжевый", "yellow_slime": "Жёлтый"
}
type_surf1 = micro_font.render(ru_names.get(slime_type, slime_type), True, (0, 0, 0))
screen.blit(type_surf1, (start_x + 5, y + 30))
jump_val = info["jump_length"]
if slime_type == "rainbow_slime":
jump_str = "2-5"
else:
jump_str = str(jump_val)
jump_surf = micro_font.render(jump_str, True, BLACK)
screen.blit(jump_surf, (start_x + col_widths[0] + 5, y + 30))
freq_text = "каждый ход" if info["jump_freq"] == 1 else "каждый 2-й"
freq_surf = micro_font.render(freq_text, True, BLACK)
screen.blit(freq_surf, (start_x + col_widths[0] + col_widths[1] + 5, y + 30))
if info["max_jumps"] is not None:
maxj_surf = micro_font.render(f"{info['max_jumps'][0]}-{info['max_jumps'][1]}", True, BLACK)
else:
maxj_surf = micro_font.render("в€ћ", True, BLACK)
screen.blit(maxj_surf, (start_x + col_widths[0] + col_widths[1] + col_widths[2] + 5, y + 30))
effect_text = info.get("death_effect")
if effect_text == "balls":
eff = "4 шара (скор.0.5)"
elif effect_text == "invis_gas":
eff = f"Невидимость R{info['gas_radius']} ({info['gas_duration']} х.)"
elif effect_text == "gas":
eff = f"РЇРґ R{info['gas_radius']} ({info['gas_duration']} С….)"
elif effect_text == "shoot":
eff = "4 шара (скор.1)"
elif effect_text == "spawn_dark_gray":
eff = "Создаёт темно-серых слизней"
elif effect_text == "spawn_light_gray":
eff = "Создаёт светло-серых слизней"
elif effect_text == "spawn_white":
eff = "Создаёт белых слизней"
elif effect_text == "lava":
eff = f"Лава R{info['lava_radius']} ({info['lava_duration']} х.)"
elif effect_text == "spawn_two":
eff = "Спавнит двух слизней (серо-зелёный или оранжевый)"
else:
eff = "-"
eff_surf = micro_font.render(eff, True, BLACK)
screen.blit(eff_surf,
(start_x + col_widths[0] + col_widths[1] + col_widths[2] + col_widths[3] + 5, y + 8))
icon_size = 32
icon_x = start_x + sum(col_widths[:5]) + 15
icon_y = y + (row_height - icon_size) // 2
slime_icon = pygame.Surface((icon_size, icon_size), pygame.SRCALPHA)
if slime_type == "rainbow_slime":
hue = (pygame.time.get_ticks() // 10) % 360
c = pygame.Color(0)
c.hsva = (hue, 100, 100)
c.a = 180
color = c
else:
color = info["color"]
pygame.draw.ellipse(slime_icon, color, (2, 2, icon_size - 4, icon_size - 4))
pygame.draw.ellipse(slime_icon, (255, 255, 255, 100), (6, 6, icon_size - 12, icon_size - 12), 1)
eye_c = (0, 0, 0, 200)
pygame.draw.circle(slime_icon, eye_c, (icon_size // 3, icon_size // 3), icon_size // 8)
pygame.draw.circle(slime_icon, eye_c, (2 * icon_size // 3, icon_size // 3), icon_size // 8)
screen.blit(slime_icon, (icon_x, icon_y))
pygame.draw.rect(screen, BLACK, (icon_x, icon_y, icon_size, icon_size), 1)
y += row_height
pygame.draw.line(screen, BLACK, (start_x - 5, y), (x + 15, y), 2)
elif bestiary_tab == 3:
for btn in tab_buttons_insects:
btn.draw(screen)
start_x = 20
start_y = 300
row_height = 80
col_widths = [150, 70, 200, 150, 100]
headers = ["Тип", "Размер", "Особенности", "Появление", "Вид"]
header_bg = (70, 70, 70)
header_text_color = (255, 255, 255)
category = TAB_NAMES_INSECTS[current_insect_tab]
insect_types = INSECT_CATEGORIES[category]
insect_types = [st for st in INSECT_INFO_ORDERED if st in insect_types]
table_width = sum(col_widths) + 40
table_height = row_height * (len(insect_types) + 1) + 20
table_x = start_x - 15
table_y = start_y - 40
pygame.draw.rect(screen, (50, 50, 50), (table_x + 5, table_y + 5, table_width, table_height),
border_radius=10)
pygame.draw.rect(screen, WHITE, (table_x, table_y, table_width, table_height), border_radius=10)
title = font.render(f"Бестиарий — {category}", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, start_y - 130))
desc_text = "Насекомые появляются с краёв поля (кроме муравейника)."
desc_surf = small_font.render(desc_text, True, BLACK)
screen.blit(desc_surf, (start_x, start_y - 80))
x = start_x
for i, header in enumerate(headers):
header_surf = micro_font.render(header, True, header_text_color)
pygame.draw.rect(screen, header_bg, (x - 5, start_y - 35, col_widths[i] + 10, row_height - 5))
screen.blit(header_surf, (x + 5, start_y - 30))
x += col_widths[i]
pygame.draw.line(screen, BLACK, (start_x - 5, start_y - 10), (x + 15, start_y - 10), 2)
y = start_y
for idx, insect_type in enumerate(insect_types):
info = INSECT_INFO[insect_type]
if idx % 2 == 0:
row_color = (240, 248, 255)
else:
row_color = (255, 255, 240)
pygame.draw.rect(screen, row_color, (start_x - 5, y - 5, x - start_x + 25, row_height))
ru_names = {
"ant": "Муравей",
"dung_beetle": "Навозник",
"stag_beetle": "Рогач",
"anthill": "Муравейник"
}
type_surf = micro_font.render(ru_names.get(insect_type, insect_type), True, info["color"])
screen.blit(type_surf, (start_x + 5, y + 30))
size_str = f"{info['size']}x{info['size']}" if insect_type == "anthill" else f"{info['size']}x{info['size']}"
size_surf = micro_font.render(size_str, True, BLACK)
screen.blit(size_surf, (start_x + col_widths[0] + 5, y + 30))
special_surf = micro_font.render(info["special"], True, BLACK)
screen.blit(special_surf, (start_x + col_widths[0] + col_widths[1] + 5, y + 30))
spawn_surf = micro_font.render(info["spawn_condition"], True, BLACK)
screen.blit(spawn_surf, (start_x + col_widths[0] + col_widths[1] + col_widths[2] + 5, y + 30))
icon_size = 32
icon_x = start_x + sum(col_widths[:4]) + 15
icon_y = y + (row_height - icon_size) // 2
insect_icon = pygame.Surface((icon_size, icon_size))
insect_icon.fill(row_color)
pygame.draw.rect(insect_icon, info["color"], (2, 2, icon_size-4, icon_size-4))
pygame.draw.rect(insect_icon, BLACK, (2, 2, icon_size-4, icon_size-4), 1)
screen.blit(insect_icon, (icon_x, icon_y))
pygame.draw.rect(screen, BLACK, (icon_x, icon_y, icon_size, icon_size), 1)
y += row_height
pygame.draw.line(screen, BLACK, (start_x - 5, y), (x + 15, y), 2)
elif bestiary_tab == 4:
for btn in tab_buttons_fish:
btn.draw(screen)
start_x = 20
start_y = 300
row_height = 70
col_widths = [150, 60, 60, 250, 70]
headers = ["Тип", "Размер", "Скорость", "Особенности", "Вид"]
header_bg = (70, 70, 70)
header_text_color = (255, 255, 255)
fish_types = FISH_INFO_ORDERED
table_width = sum(col_widths) + 40
table_height = row_height * (len(fish_types) + 1) + 20
table_x = start_x - 15
table_y = start_y - 40
pygame.draw.rect(screen, (50, 50, 50), (table_x + 5, table_y + 5, table_width, table_height), border_radius=10)
pygame.draw.rect(screen, WHITE, (table_x, table_y, table_width, table_height), border_radius=10)
title = font.render("Бестиарий — Рыбы", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, start_y - 130))
desc_text = "Рыбы плавают под водой (тенью) и выпрыгивают, чтобы атаковать."
desc_surf = small_font.render(desc_text, True, BLACK)
screen.blit(desc_surf, (start_x, start_y - 80))
x = start_x
for i, header in enumerate(headers):
header_surf = micro_font.render(header, True, header_text_color)
pygame.draw.rect(screen, header_bg, (x - 5, start_y - 35, col_widths[i] + 10, row_height - 5))
screen.blit(header_surf, (x + 5, start_y - 30))
x += col_widths[i]
pygame.draw.line(screen, BLACK, (start_x - 5, start_y - 10), (x + 15, start_y - 10), 2)
y = start_y
for idx, fish_type in enumerate(fish_types):
info = FISH_INFO[fish_type]
if idx % 2 == 0:
row_color = (240, 248, 255)
else:
row_color = (255, 255, 240)
pygame.draw.rect(screen, row_color, (start_x - 5, y - 5, x - start_x + 25, row_height))
ru_names = {
"swallow": "Рыба-ласточка",
"swordfish": "Рыба-меч",
"shark": "Акула",
"octopus": "РћСЃСЊРјРёРЅРѕРі"
}
type_surf = micro_font.render(ru_names.get(fish_type, fish_type), True, info["color"])
screen.blit(type_surf, (start_x + 5, y + 25))
size_surf = micro_font.render(str(info["size"]), True, BLACK)
screen.blit(size_surf, (start_x + col_widths[0] + 5, y + 25))
speed_surf = micro_font.render("1", True, BLACK)
screen.blit(speed_surf, (start_x + col_widths[0] + col_widths[1] + 5, y + 25))
special_surf = micro_font.render(info["description"], True, BLACK)
screen.blit(special_surf, (start_x + col_widths[0] + col_widths[1] + col_widths[2] + 5, y + 8))
icon_size = 32
icon_x = start_x + sum(col_widths[:4]) + 15
icon_y = y + (row_height - icon_size) // 2
pygame.draw.rect(screen, info["color"], (icon_x, icon_y, icon_size, icon_size))
pygame.draw.rect(screen, BLACK, (icon_x, icon_y, icon_size, icon_size), 1)
y += row_height
pygame.draw.line(screen, BLACK, (start_x - 5, y), (x + 15, y), 2)
else:
for btn in tab_buttons_other:
btn.draw(screen)
start_x = 50
start_y = 300
row_height = 60
if current_other_tab == 0:
gases = GASES_INFO
col_widths = [200, 60, 350]
headers = ["Название", "Цвет", "Описание"]
header_bg = (70, 70, 70)
header_text_color = (255, 255, 255)
table_width = sum(col_widths) + 40
table_height = row_height * (len(gases) + 1) + 20
table_x = start_x - 15
table_y = start_y - 40
pygame.draw.rect(screen, (50, 50, 50), (table_x + 5, table_y + 5, table_width, table_height),
border_radius=10)
pygame.draw.rect(screen, WHITE, (table_x, table_y, table_width, table_height), border_radius=10)
title = font.render("Газы", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, start_y - 130))
x = start_x
for i, header in enumerate(headers):
header_surf = micro_font.render(header, True, header_text_color)
pygame.draw.rect(screen, header_bg, (x - 5, start_y - 35, col_widths[i] + 10, row_height - 5))
screen.blit(header_surf, (x + 5, start_y - 30))
x += col_widths[i]
pygame.draw.line(screen, BLACK, (start_x - 5, start_y - 10), (x + 15, start_y - 10), 2)
y = start_y
for idx, gas in enumerate(gases):
if idx % 2 == 0:
row_color = (240, 248, 255)
else:
row_color = (255, 255, 240)
pygame.draw.rect(screen, row_color, (start_x - 5, y - 5, x - start_x + 25, row_height))
name_surf = small_font.render(gas["name"], True, BLACK)
screen.blit(name_surf, (start_x + 5, y + 15))
color_rect = pygame.Rect(start_x + col_widths[0] + 10, y + 15, 30, 30)
pygame.draw.rect(screen, gas["color"], color_rect)
pygame.draw.rect(screen, BLACK, color_rect, 1)
desc_surf = micro_font.render(gas["description"], True, BLACK)
screen.blit(desc_surf, (start_x + col_widths[0] + col_widths[1] + 5, y + 15))
y += row_height
pygame.draw.line(screen, BLACK, (start_x - 5, y), (x + 15, y), 2)
elif current_other_tab == 1:
projs = PROJECTILES_INFO
col_widths = [200, 60, 350]
headers = ["Название", "Цвет", "Описание"]
header_bg = (70, 70, 70)
header_text_color = (255, 255, 255)
table_width = sum(col_widths) + 40
table_height = row_height * (len(projs) + 1) + 20
table_x = start_x - 15
table_y = start_y - 40
pygame.draw.rect(screen, (50, 50, 50), (table_x + 5, table_y + 5, table_width, table_height),
border_radius=10)
pygame.draw.rect(screen, WHITE, (table_x, table_y, table_width, table_height), border_radius=10)
title = font.render("Снаряды", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, start_y - 130))
x = start_x
for i, header in enumerate(headers):
header_surf = micro_font.render(header, True, header_text_color)
pygame.draw.rect(screen, header_bg, (x - 5, start_y - 35, col_widths[i] + 10, row_height - 5))
screen.blit(header_surf, (x + 5, start_y - 30))
x += col_widths[i]
pygame.draw.line(screen, BLACK, (start_x - 5, start_y - 10), (x + 15, start_y - 10), 2)
y = start_y
for idx, proj in enumerate(projs):
if idx % 2 == 0:
row_color = (240, 248, 255)
else:
row_color = (255, 255, 240)
pygame.draw.rect(screen, row_color, (start_x - 5, y - 5, x - start_x + 25, row_height))
name_surf = small_font.render(proj["name"], True, BLACK)
screen.blit(name_surf, (start_x + 5, y + 15))
color_rect = pygame.Rect(start_x + col_widths[0] + 10, y + 15, 30, 30)
pygame.draw.rect(screen, proj["color"], color_rect)
pygame.draw.rect(screen, BLACK, color_rect, 1)
pygame.draw.circle(screen, proj["color"], color_rect.center, 12)
pygame.draw.circle(screen, WHITE, (color_rect.centerx - 3, color_rect.centery - 3), 3)
desc_surf = micro_font.render(proj["description"], True, BLACK)
screen.blit(desc_surf, (start_x + col_widths[0] + col_widths[1] + 5, y + 15))
y += row_height
pygame.draw.line(screen, BLACK, (start_x - 5, y), (x + 15, y), 2)
elif current_other_tab == 2:
abilities_list = ABILITY_INFO
col_widths = [180, 50, 50, 50, 300]
headers = ["Название", "Клав.", "РСЃРї.", "РљР”", "Описание"]
header_bg = (70, 70, 70)
header_text_color = (255, 255, 255)
table_width = sum(col_widths) + 40
table_height = row_height * (len(abilities_list) + 1) + 20
table_x = start_x - 15
table_y = start_y - 40
pygame.draw.rect(screen, (50, 50, 50), (table_x + 5, table_y + 5, table_width, table_height),
border_radius=10)
pygame.draw.rect(screen, WHITE, (table_x, table_y, table_width, table_height), border_radius=10)
title = font.render("Способности", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, start_y - 130))
x = start_x
for i, header in enumerate(headers):
header_surf = micro_font.render(header, True, header_text_color)
pygame.draw.rect(screen, header_bg, (x - 5, start_y - 35, col_widths[i] + 10, row_height - 5))
screen.blit(header_surf, (x + 5, start_y - 30))
x += col_widths[i]
pygame.draw.line(screen, BLACK, (start_x - 5, start_y - 10), (x + 15, start_y - 10), 2)
y = start_y
for idx, ab in enumerate(abilities_list):
if idx % 2 == 0:
row_color = (240, 248, 255)
else:
row_color = (255, 255, 240)
pygame.draw.rect(screen, row_color, (start_x - 5, y - 5, x - start_x + 25, row_height))
name_surf = small_font.render(ab["name"], True, BLACK)
screen.blit(name_surf, (start_x + 5, y + 15))
key_surf = micro_font.render(str(ab["key"]), True, BLACK)
screen.blit(key_surf, (start_x + col_widths[0] + 5, y + 15))
uses_surf = micro_font.render(str(ab["uses"]), True, BLACK)
screen.blit(uses_surf, (start_x + col_widths[0] + col_widths[1] + 5, y + 15))
cd_surf = micro_font.render(str(ab["cooldown"]), True, BLACK)
screen.blit(cd_surf, (start_x + col_widths[0] + col_widths[1] + col_widths[2] + 5, y + 15))
desc_surf = micro_font.render(ab["description"], True, BLACK)
screen.blit(desc_surf,
(start_x + col_widths[0] + col_widths[1] + col_widths[2] + col_widths[3] + 5, y + 15))
y += row_height
pygame.draw.line(screen, BLACK, (start_x - 5, y), (x + 15, y), 2)
else:
modifiers_list = list(MODIFIERS_INFO.items())
col_widths = [250, 500]
headers = ["Усложнение", "Описание"]
header_bg = (70, 70, 70)
header_text_color = (255, 255, 255)
table_width = sum(col_widths) + 40
table_height = row_height * (len(modifiers_list) + 1) + 20
table_x = start_x - 15
table_y = start_y - 40
pygame.draw.rect(screen, (50, 50, 50), (table_x + 5, table_y + 5, table_width, table_height),
border_radius=10)
pygame.draw.rect(screen, WHITE, (table_x, table_y, table_width, table_height), border_radius=10)
title = font.render("Усложнения (песочница)", True, BLACK)
screen.blit(title, (WIDTH // 2 - title.get_width() // 2, start_y - 130))
x = start_x
for i, header in enumerate(headers):
header_surf = micro_font.render(header, True, header_text_color)
pygame.draw.rect(screen, header_bg, (x - 5, start_y - 35, col_widths[i] + 10, row_height - 5))
screen.blit(header_surf, (x + 5, start_y - 30))
x += col_widths[i]
pygame.draw.line(screen, BLACK, (start_x - 5, start_y - 10), (x + 15, start_y - 10), 2)
y = start_y
for idx, (key, desc) in enumerate(modifiers_list):
if idx % 2 == 0:
row_color = (240, 248, 255)
else:
row_color = (255, 255, 240)
pygame.draw.rect(screen, row_color, (start_x - 5, y - 5, x - start_x + 25, row_height))
name_surf = small_font.render(key.replace('_', ' ').capitalize(), True, BLACK)
screen.blit(name_surf, (start_x + 5, y + 15))
words = desc.split()
lines = []
cur_line = ""
for w in words:
if len(cur_line) + len(w) + 1 <= 60:
if cur_line:
cur_line += " "
cur_line += w
else:
lines.append(cur_line)
cur_line = w
if cur_line:
lines.append(cur_line)
for li, line in enumerate(lines[:3]):
desc_surf = micro_font.render(line, True, (50, 50, 50))
screen.blit(desc_surf, (start_x + col_widths[0] + 5, y + 5 + li * 18))
y += row_height
pygame.draw.line(screen, BLACK, (start_x - 5, y), (x + 15, y), 2)
back_button.draw(screen)
pygame.display.flip()
# ---------------------- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦРР ----------------------
def go_to_game(*args):
global current_state
current_state = "game"
def go_to_bestiary(*args):
global current_state
current_state = "bestiary"
def go_to_level_select():
global current_state
current_state = "level_select"
def go_to_challenge_select():
global current_state
current_state = "challenge_select"
def go_to_survival_select():
global current_state
current_state = "survival_select"
def go_to_controls(*args):
global current_state
current_state = "controls"
def go_to_menu(*args):
global current_state
current_state = "menu"
def go_to_mode_select(*args):
global current_state
current_state = "mode_select"
def go_to_skin_select(*args):
global current_state
current_state = "skin_select"
def go_to_settings(*args):
global current_state
current_state = "settings"
def go_to_sandbox_menu(*args):
global current_state
current_state = "sandbox_menu"
def exit_game(*args):
pygame.quit()
sys.exit()
# ---------------------- РћРЎРќРћР’РќРћР™ Р¦РРљР› ----------------------
current_state = "menu"
game_mode = None
selected_abilities = {}
running = True
while running:
if current_state == "menu":
show_menu()
elif current_state == "mode_select":
show_game_mode_selection()
elif current_state == "level_select":
show_level_select()
elif current_state == "level_info":
show_level_info(current_level)
elif current_state == "challenge_select":
show_challenge_select()
elif current_state == "survival_select":
show_survival_select()
elif current_state == "ability_selection":
show_ability_selection(game_mode)
elif current_state == "game":
show_game()
elif current_state == "spider_game":
show_spider_game()
elif current_state == "slime_game":
show_slime_game()
elif current_state == "insect_game":
show_insect_game()
elif current_state == "fish_game":
show_fish_game()
elif current_state == "level1":
show_level_game(1)
elif current_state == "level2":
show_level_game(2)
elif current_state == "level3":
show_level_game(3)
elif current_state == "level4":
show_level_game(4)
elif current_state == "level5":
show_level_game(5)
elif current_state == "level6":
show_level_game(6)
elif current_state == "level7":
show_level_game(7)
elif current_state == "level8":
show_level_game(8)
elif current_state == "level9":
show_level_game(9)
elif current_state == "level10":
show_level_game(10)
elif current_state == "level11":
show_level_game(11)
elif current_state == "level12":
show_level_game(12)
elif current_state == "level13":
show_level_game(13)
elif current_state == "level14":
show_level_game(14)
elif current_state == "level15":
show_level_game(15)
elif current_state == "level16":
show_level_game(16)
elif current_state == "level17":
show_level_game(17)
elif current_state == "level18":
show_level_game(18)
elif current_state == "level19":
show_level_game(19)
elif current_state == "level20":
show_level_game(20)
elif current_state == "challenge1":
show_challenge1()
elif current_state == "challenge2":
show_challenge2()
elif current_state == "challenge3":
show_challenge3()
elif current_state == "challenge4":
show_challenge4()
elif current_state == "challenge5":
show_challenge5()
elif current_state == "bestiary":
show_bestiary()
elif current_state == "controls":
show_controls()
elif current_state == "skin_select":
show_skin_select()
elif current_state == "settings":
show_settings()
elif current_state == "sandbox_menu":
show_sandbox_menu()
elif current_state == "sandbox_settings":
show_sandbox_settings()
elif current_state == "sandbox_modifiers":
show_sandbox_modifiers()
elif current_state == "sandbox_arena":
show_sandbox_arena_selection()
elif current_state == "sandbox_play":
show_sandbox_play()
else:
current_state = "menu"
pygame.quit()
sys.exit()