Участник:Prionosuchus

Материал из Абсурдопедии
Перейти к навигацииПерейти к поиску

Prionosuchus — древняя амфибия, жившая в пермском периоде (т.е. до Хренозоя).

Коды для игр[править]

(потом перемещу их в нужную статью, а пока проверяю их здесь)

Щучье озеро[править]

  • Щучье озеро — симулятор поведения щук, когда они единственные рыбы водоёма.
    • Геймплей: спамить икру или молодых щук, начинать и заканчивать зимний период.
    • Цель игры — узнать на какой по счёту рыбе ваш комп взорвётся. Ну или помедитировать на бесконечно плодящихся, друг друга жрущих и умирающих щук.
import pygame

import random

import math

pygame.init()

WIDTH, HEIGHT = 1400, 1000

screen = pygame.display.set_mode((WIDTH, HEIGHT))

pygame.display.set_caption("Щучье озеро (вид сверху)")

clock = pygame.time.Clock()

FPS = 60

SAND_COLOR = (210, 180, 140)

WATER_CENTER_COLOR = (20, 80, 180)

BLUE = (50, 150, 250)

GREEN = (0, 150, 0)

BLACK = (0, 0, 0)

WHITE = (255, 255, 255)

RED = (200, 0, 0)

DARK_GREEN = (0, 100, 0)

YELLOW = (255, 255, 0)

ORANGE = (255, 165, 0)

MAX_LEECHES = 1000

MAX_EGGS = 3000

EGG_LIFETIME = 10000

GRID_DISPLAY_TIME = 1000

STARVATION_TIME = 60000

DEATH_SPAWN_EGGS = 20

SHORE_WIDTH = 80

SHORE_WIDTH_SMALL = 20         

DARKEN_RECT_W = 340            

DARKEN_RECT_H = 300            

DARKEN_ALPHA = 80 

DARKEN_RECT = pygame.Rect(

    WIDTH // 2 - DARKEN_RECT_W // 2,

    HEIGHT // 2 - DARKEN_RECT_H // 2,

    DARKEN_RECT_W,

    DARKEN_RECT_H

)

font = pygame.font.SysFont("Arial", 20)

big_font = pygame.font.SysFont("Arial", 36)

speech_font = pygame.font.SysFont("Arial", 18)

help_font = pygame.font.SysFont("Arial", 16)

PHRASES_EAT_LEECH = ["Вкуснятина!", "Ням-ням!", "Пиявка - вкусно!", "Ммм, пиявка!"]

PHRASES_EAT_PIKE = ["Ты слишком мал!", "Каннибализм!", "Я сильнее!", "Ай, как нехорошо!"]

PHRASES_EAT_EGG = ["Икорка!", "Вкусные яйца!", "Ням!"]

PHRASES_SPAWN = ["Потомство будет!", "Я папа!", "Я мама!", "Пора размножаться!"]

PHRASES_DEATH_OLD = ["Я устал, ухожу...", "Прощайте, потомки!", "Старость не радость...", "Тима раков, я ливаю!"]

PHRASES_DEATH_STARVE = ["Я голодал...", "Нет еды...", "Помогите!"]

PHRASES_CAUGHT = ["Ой, блесна!", "Попался!", "Твою дивизию!"]

PHRASES_WINTER = ["Капеееееец", "В спячку!", "Зима близко!", "Холодно"]

PHRASES_POST_WINTER = ["Весна пришла!", "Пора нереститься!", "Рота подъём!!!"]

PHRASES_FLEE = ["Я сделал дело, ухожу!", "Пока!", "Ну его нафиг!"]

PHRASES_RANDOM = ["Где еда?", "Я голоден", "Плыву", "Ой, что-то блестит", "Хочу пиявку", "Кто там?"]

PHRASES_FRY_SPAWN = ["Новая жизнь!", "Вперёд!!!", "Я родился!"]

MAX_MESSAGES = 5 
        
messages = []

def add_message(text, x, y, duration=2000, probability=1.0):

    if random.random() > probability:

        return

    global messages

    if len(messages) >= MAX_MESSAGES:

        messages.pop(0)

    messages.append({'text': text, 'x': x, 'y': y, 'timer': pygame.time.get_ticks() + duration})

def random_leech_position():

    if random.random() < 0.7:

        side = random.randint(0, 3)

        if side == 0:

            x = random.uniform(SHORE_WIDTH, 2 * SHORE_WIDTH)

            y = random.uniform(SHORE_WIDTH, HEIGHT - SHORE_WIDTH)

        elif side == 1:
            x = random.uniform(WIDTH - 2 * SHORE_WIDTH, WIDTH - SHORE_WIDTH)

            y = random.uniform(SHORE_WIDTH, HEIGHT - SHORE_WIDTH)

        elif side == 2:

            x = random.uniform(SHORE_WIDTH, WIDTH - SHORE_WIDTH)

            y = random.uniform(SHORE_WIDTH, 2 * SHORE_WIDTH)

        else: 

            x = random.uniform(SHORE_WIDTH, WIDTH - SHORE_WIDTH)

            y = random.uniform(HEIGHT - 2 * SHORE_WIDTH, HEIGHT - SHORE_WIDTH)

    else:

        x = random.uniform(2 * SHORE_WIDTH, WIDTH - 2 * SHORE_WIDTH)

        y = random.uniform(2 * SHORE_WIDTH, HEIGHT - 2 * SHORE_WIDTH)

    return x, y

def random_water_position(small=False):

    limit = SHORE_WIDTH_SMALL if small else SHORE_WIDTH

    x = random.uniform(limit, WIDTH - limit)

    y = random.uniform(limit, HEIGHT - limit)

    return x, y

def create_water_surface(width, height, shore_width):

    surf = pygame.Surface((width, height), pygame.SRCALPHA)

    water_color = WATER_CENTER_COLOR

    surf.fill((water_color[0], water_color[1], water_color[2], 255))

    for y in range(height):

        for x in range(width):

            dx = min(x, width - 1 - x)

            dy = min(y, height - 1 - y)

            d = min(dx, dy)

            if d < shore_width:

                alpha = int(255 * (d / shore_width))

                surf.set_at((x, y), (water_color[0], water_color[1], water_color[2], alpha))

    return surf

sand_surf = pygame.Surface((WIDTH, HEIGHT))

sand_surf.fill(SAND_COLOR)

water_surf = create_water_surface(WIDTH, HEIGHT, SHORE_WIDTH)

winter_active = False

ice_surf = None

def create_ice_surface(width, height):

    """Создаёт полупрозрачную ледяную поверхность."""

    surf = pygame.Surface((width, height), pygame.SRCALPHA)

    surf.fill((200, 230, 255, 80))

    for _ in range(500):

        x = random.randint(0, width - 1)

        y = random.randint(0, height - 1)

        alpha = random.randint(30, 100)

        surf.set_at((x, y), (255, 255, 255, alpha))

    return surf

def toggle_winter():

    global winter_active, ice_surf, leeches, pikes, eggs

    if not winter_active:

        winter_active = True

        ice_surf = create_ice_surface(WIDTH, HEIGHT)

        if leeches:

            leeches = random.sample(leeches, max(1, int(len(leeches) * 0.2)))

        pikes = [p for p in pikes if p.length >= 70]

        center_rect = DARKEN_RECT

        for pike in pikes:

            pike.state = "wintering"

            pike.target_point = (

                random.uniform(center_rect.left, center_rect.right),

                random.uniform(center_rect.top, center_rect.bottom)

            )

            pike.current_speed = pike.base_speed

            pike.target = None

            pike.has_post_winter_spawned = False

            add_message(random.choice(PHRASES_WINTER), pike.x, pike.y, probability=1.0)

    else:

        winter_active = False

        ice_surf = None

        for pike in pikes:

            if pike.state == "wintering":

                pike.state = "post_winter"

                side = random.randint(0, 3)

                if side == 0:

                    x = random.uniform(SHORE_WIDTH, 2 * SHORE_WIDTH)

                    y = random.uniform(SHORE_WIDTH, HEIGHT - SHORE_WIDTH)

                elif side == 1:

                    x = random.uniform(WIDTH - 2 * SHORE_WIDTH, WIDTH - SHORE_WIDTH)

                    y = random.uniform(SHORE_WIDTH, HEIGHT - SHORE_WIDTH)

                elif side == 2:

                    x = random.uniform(SHORE_WIDTH, WIDTH - SHORE_WIDTH)

                    y = random.uniform(SHORE_WIDTH, 2 * SHORE_WIDTH)

                else:

                    x = random.uniform(SHORE_WIDTH, WIDTH - SHORE_WIDTH)

                    y = random.uniform(HEIGHT - 2 * SHORE_WIDTH, HEIGHT - SHORE_WIDTH)

                pike.target_point = (x, y)

                pike.current_speed = pike.base_speed

                pike.target = None

                pike.has_post_winter_spawned = False

                add_message(random.choice(PHRASES_POST_WINTER), pike.x, pike.y, probability=1.0)

class Leech:

    def __init__(self, x, y):

        self.x = x

        self.y = y

        self.length = random.randint(2, 15)

        self.angle = random.uniform(0, 2 * math.pi)

        self.speed = 0.9

        self.segments = 4

        self.time = random.random() * 100

    def update(self):

        if random.random() < 0.02:

            self.angle += random.uniform(-0.5, 0.5)

        self.x += self.speed * math.cos(self.angle)

        self.y += self.speed * math.sin(self.angle)

        if self.x < 0:

            self.x = 0

            self.angle = math.pi - self.angle

        if self.x > WIDTH:

            self.x = WIDTH

            self.angle = math.pi - self.angle

        if self.y < 0:

            self.y = 0

            self.angle = -self.angle

        if self.y > HEIGHT:

            self.y = HEIGHT

            self.angle = -self.angle

        self.time += 0.1

    def draw(self, screen):

        points = []

        step = self.length / self.segments

        x, y = self.x, self.y

        angle = self.angle

        for i in range(self.segments + 1):

            points.append((x, y))

            bend = 0.3 * math.sin(self.time + i * 0.5)

            angle += bend * 0.1

            x += step * math.cos(angle)

            y += step * math.sin(angle)

        if len(points) > 1:

            pygame.draw.lines(screen, BLACK, False, points, 1)

class Egg:

    def __init__(self, x, y):

        self.x = x

        self.y = y

        self.radius = 3

        self.born_time = pygame.time.get_ticks()

    def update(self, pikes):

        if pygame.time.get_ticks() - self.born_time >= EGG_LIFETIME:

            fry_x = max(SHORE_WIDTH_SMALL, min(WIDTH - SHORE_WIDTH_SMALL, self.x))

            fry_y = max(SHORE_WIDTH_SMALL, min(HEIGHT - SHORE_WIDTH_SMALL, self.y))

            fry = Pike(fry_x, fry_y, initial_length=8, state="fleeing_from_spawn")

            angle = random.uniform(0, 2 * math.pi)

            dist = 50

            fry.target_point = (fry_x + dist * math.cos(angle), fry_y + dist * math.sin(angle))

            pikes.append(fry)

            add_message(random.choice(PHRASES_FRY_SPAWN), fry.x, fry.y, probability=1.0)

            return True

        return False

    def draw(self, screen):

        surf = pygame.Surface((self.radius * 2, self.radius * 2), pygame.SRCALPHA)

        pygame.draw.circle(surf, (0, 200, 0, 128), (self.radius, self.radius), self.radius)

        pygame.draw.circle(surf, (0, 100, 0), (self.radius, self.radius), self.radius, 1)

        screen.blit(surf, (self.x - self.radius, self.y - self.radius))

class Bait:

    def __init__(self, x, y):

        self.x = x

        self.y = y

        self.width = 12

        self.height = 8

        self.speed = 5

    def update(self, keys):

        if keys[pygame.K_UP]:

            self.y -= self.speed

        if keys[pygame.K_DOWN]:

            self.y += self.speed

        if keys[pygame.K_LEFT]:

            self.x -= self.speed

        if keys[pygame.K_RIGHT]:

            self.x += self.speed

        self.x = max(SHORE_WIDTH + self.width // 2, min(WIDTH - SHORE_WIDTH - self.width // 2, self.x))

        self.y = max(SHORE_WIDTH + self.height // 2, min(HEIGHT - SHORE_WIDTH - self.height // 2, self.y))

    def draw(self, screen):

        pygame.draw.ellipse(screen, RED, (self.x - self.width // 2, self.y - self.height // 2, self.width, self.height))

class Pike:

    def __init__(self, x, y, initial_length=12, state="normal"):

        self.x = x

        self.y = y

        self.length = initial_length

        self.width = max(5, self.length / 7)

        self.angle = random.uniform(0, 2 * math.pi)

        self.base_speed = self.calc_base_speed(initial_length)

        self.current_speed = self.base_speed

        self.target = None

        self.state = state

        self.target_point = None

        self.max_length_reached = False

        self.time_at_max = 0

        self.last_eat_egg_time = 0

        self.last_meal_time = pygame.time.get_ticks()

        self.last_update_time = pygame.time.get_ticks()

        self.has_spawned_on_death = False

        self.has_post_winter_spawned = False

        self.last_random_speech = random.randint(0, 5000)

        self.generate_sprite()

    def calc_base_speed(self, length):

        return 3.0 - (length - 8) / 152 * 1.0

    def get_shore_limit(self):

        return SHORE_WIDTH_SMALL if self.length <= 29 else SHORE_WIDTH

    def generate_sprite(self):

        w = int(self.length)

        h = int(self.width)

        if w < 5 or h < 3:

            surf = pygame.Surface((w, h), pygame.SRCALPHA)

            pygame.draw.ellipse(surf, GREEN, (0, 0, w, h))

            self.sprite = surf

            return

        surf = pygame.Surface((w, h), pygame.SRCALPHA)

        body_color = (30, 80, 30)

        pygame.draw.ellipse(surf, body_color, (0, 0, w, h))

        spine_color = (20, 60, 20)

        belly_color = (30, 80, 30)

        spine_rect = pygame.Rect(0, 0, w, h // 2 + 2)

        pygame.draw.ellipse(surf, spine_color, spine_rect)

        belly_rect = pygame.Rect(0, h // 2 - 2, w, h // 2 + 2)

        pygame.draw.ellipse(surf, belly_color, belly_rect)

        eye_radius = max(2, int(0.09 * h))

        pupil_radius = max(1, int(0.05 * h))

        eye_x = int(0.8 * w)

        eye_y1 = int(0.35 * h)

        eye_y2 = int(0.65 * h)

        pygame.draw.circle(surf, WHITE, (eye_x, eye_y1), eye_radius)

        pygame.draw.circle(surf, WHITE, (eye_x, eye_y2), eye_radius)

        pupil_offset = int(0.2 * eye_radius)

        pygame.draw.circle(surf, BLACK, (eye_x + pupil_offset, eye_y1), pupil_radius)

        pygame.draw.circle(surf, BLACK, (eye_x + pupil_offset, eye_y2), pupil_radius)

        dorsal_color = (20, 60, 20)

        dorsal_x = int(0.2 * w)

        dorsal_w = int(0.5 * w)

        dorsal_h = max(2, int(h * 0.25))

        pygame.draw.ellipse(surf, dorsal_color, (dorsal_x, 0, dorsal_w, dorsal_h))

        pectoral_color = (20, 60, 20)

        pect_w = int(0.06 * w)

        pect_h = int(0.15 * h)

        pygame.draw.ellipse(surf, pectoral_color, (int(0.6 * w), int(0.05 * h), pect_w, pect_h))

        pygame.draw.ellipse(surf, pectoral_color, (int(0.6 * w), int(0.8 * h), pect_w, pect_h))

        tail_color = ORANGE

        tail_w = int(0.3 * w)

        tail_h = int(0.9 * h)

        tail_x = -tail_w // 2

        tail_y = (h - tail_h) // 2

        pygame.draw.ellipse(surf, tail_color, (tail_x, tail_y, tail_w, tail_h))

        num_spots = random.randint(3, 6)

        for _ in range(num_spots):

            cx = random.uniform(0.2, 0.8) * w

            cy = random.uniform(0.15, 0.4) * h

            rx = random.uniform(0.02, 0.08) * w

            ry = random.uniform(0.02, 0.08) * h

            color = random.choice([(0, 30, 0), (10, 50, 10), (20, 70, 20)])

            pygame.draw.ellipse(surf, color, (int(cx - rx / 2), int(cy - ry / 2), int(rx), int(ry)))

            pygame.draw.ellipse(surf, color, (int(cx - rx / 2), int(h - cy - ry / 2), int(rx), int(ry)))

        for _ in range(random.randint(2, 4)):

            cx = random.uniform(0.3, 0.7) * w

            cy = random.uniform(0.05, 0.2) * h

            rx = random.uniform(0.02, 0.06) * w

            ry = random.uniform(0.01, 0.04) * h

            pygame.draw.ellipse(surf, (0, 20, 0), (int(cx - rx / 2), int(cy - ry / 2), int(rx), int(ry)))

        self.sprite = surf

    def get_tint(self):

        if DARKEN_RECT.collidepoint(self.x, self.y):

            return (0, 0, 0, DARKEN_ALPHA)

        return (0, 0, 0, 0)

    def find_target(self, leeches, pikes, eggs, bait):

        if self.state != "normal":

            self.target = None

            return

        if self.length >= 160 and bait is not None:

            self.target = bait

            return

        min_dist = float('inf')

        target = None

        if self.length < 40:

            for leech in leeches:

                dist = math.hypot(self.x - leech.x, self.y - leech.y)

                if dist < min_dist:

                    min_dist = dist

                    target = leech

        if self.length >= 30:

            for other in pikes:

                if other is self:

                    continue

                if other.length < self.length / 2:

                    dist = math.hypot(self.x - other.x, self.y - other.y)

                    if dist < min_dist:

                        min_dist = dist

                        target = other

        if min_dist == float('inf') or min_dist > 200:

            for egg in eggs:

                dist = math.hypot(self.x - egg.x, self.y - egg.y)

                if dist < min_dist:

                    min_dist = dist

                    target = egg

        self.target = target

    def update(self, leeches, pikes, eggs, bait, current_time):

        dt = current_time - self.last_update_time

        self.last_update_time = current_time

        limit = self.get_shore_limit()

        if self.state == "wintering":

            if self.target_point is not None:

                tx, ty = self.target_point

                dx = tx - self.x

                dy = ty - self.y

                dist = math.hypot(dx, dy)

                if dist > 0:

                    self.angle = math.atan2(dy, dx)

                    if dist < self.current_speed:

                        self.x = tx

                        self.y = ty

                        self.current_speed = 0

                    else:

                        self.x += self.current_speed * dx / dist

                        self.y += self.current_speed * dy / dist

            self.x = max(limit, min(WIDTH - limit, self.x))

            self.y = max(limit, min(HEIGHT - limit, self.y))

            return

        if self.state == "post_winter":

            if self.target_point is not None:

                tx, ty = self.target_point

                dx = tx - self.x

                dy = ty - self.y

                dist = math.hypot(dx, dy)

                if dist > 0:

                    self.angle = math.atan2(dy, dx)

                    if dist < self.current_speed:

                        self.x = tx

                        self.y = ty

                        if not self.has_post_winter_spawned:

                            if self.length >= 50 and len(eggs) < MAX_EGGS:

                                num_eggs = int(0.5 * self.length)

                                for _ in range(num_eggs):

                                    angle = random.uniform(0, 2 * math.pi)

                                    dist2 = random.uniform(10, 30)

                                    ex = self.x + dist2 * math.cos(angle)

                                    ey = self.y + dist2 * math.sin(angle)

                                    ex = max(SHORE_WIDTH + 5, min(WIDTH - SHORE_WIDTH - 5, ex))

                                    ey = max(SHORE_WIDTH + 5, min(HEIGHT - SHORE_WIDTH - 5, ey))

                                    eggs.append(Egg(ex, ey))

                                add_message(random.choice(PHRASES_SPAWN), self.x, self.y, probability=1.0)

                            self.has_post_winter_spawned = True

                        self.state = "normal"

                        self.target_point = None

                        self.current_speed = self.base_speed

                    else:

                        self.x += self.current_speed * dx / dist

                        self.y += self.current_speed * dy / dist

            self.x = max(limit, min(WIDTH - limit, self.x))

            self.y = max(limit, min(HEIGHT - limit, self.y))

            return

        if self.state in ("fleeing_from_spawn", "fleeing_from_eggs"):

            if self.target_point is not None:

                tx, ty = self.target_point

                dx = tx - self.x

                dy = ty - self.y

                dist = math.hypot(dx, dy)

                if dist > 0:

                    self.angle = math.atan2(dy, dx)

                    if dist < self.current_speed:

                        self.x = tx

                        self.y = ty

                    else:

                        self.x += self.current_speed * dx / dist

                        self.y += self.current_speed * dy / dist

                if dist <= self.current_speed + 2:

                    self.state = "normal"

                    self.target_point = None

                    self.current_speed = self.base_speed

            else:

                self.state = "normal"

            self.x = max(limit, min(WIDTH - limit, self.x))

            self.y = max(limit, min(HEIGHT - limit, self.y))

            return

        self.find_target(leeches, pikes, eggs, bait)

        if self.target is not None:

            dx = self.target.x - self.x

            dy = self.target.y - self.y

            dist = math.hypot(dx, dy)

            if dist > 0:

                self.angle = math.atan2(dy, dx)

                if dist < self.current_speed:

                    self.x = self.target.x

                    self.y = self.target.y

                else:

                    self.x += self.current_speed * dx / dist

                    self.y += self.current_speed * dy / dist

        self.x = max(limit, min(WIDTH - limit, self.x))

        self.y = max(limit, min(HEIGHT - limit, self.y))

        if self.state == "normal" and current_time - self.last_random_speech > 5000:

            if random.random() < 0.01:

                phrase = random.choice(PHRASES_RANDOM)

                add_message(phrase, self.x, self.y, duration=1500, probability=1.0)

            self.last_random_speech = current_time

    def check_eat(self, leeches, pikes, eggs, bait, current_time):

        if self.state != "normal":

            return False

        if self.length >= 160 and bait is not None:

            dist = math.hypot(self.x - bait.x, self.y - bait.y)

            if dist < max(10, self.length / 2 + 5):

                add_message(random.choice(PHRASES_CAUGHT), self.x, self.y, probability=1.0)

                return "bait_eaten"

        if self.length < 40:

            for leech in leeches:

                dist = math.hypot(self.x - leech.x, self.y - leech.y)

                if dist < max(10, self.length / 2 + 5):

                    leeches.remove(leech)

                    self.last_meal_time = current_time

                    if not self.max_length_reached:

                        self.length += leech.length

                        self.width = max(5, self.length / 7)

                        self.generate_sprite()

                    prob = 1.0 if self.length > 50 else 0.5

                    add_message(random.choice(PHRASES_EAT_LEECH), self.x, self.y, probability=prob)

                    return True

        if self.length >= 30:

            for other in pikes:

                if other is self:

                    continue

                if other.length < self.length / 2:

                    dist = math.hypot(self.x - other.x, self.y - other.y)

                    if dist < max(10, self.length / 2 + 5):

                        pikes.remove(other)

                        self.last_meal_time = current_time

                        if not self.max_length_reached:

                            self.length += other.length

                            self.width = max(5, self.length / 7)

                            self.generate_sprite()

                        prob = 1.0 if self.length > 50 else 0.5

                        add_message(random.choice(PHRASES_EAT_PIKE), self.x, self.y, probability=prob)

                        return True

        if current_time - self.last_eat_egg_time >= 1000:

            for egg in eggs:

                dist = math.hypot(self.x - egg.x, self.y - egg.y)

                if dist < max(10, self.length / 2 + 5):

                    eggs.remove(egg)

                    self.last_eat_egg_time = current_time

                    self.last_meal_time = current_time

                    if not self.max_length_reached:

                        self.length += 1

                        self.width = max(5, self.length / 7)

                        self.generate_sprite()

                    prob = 1.0 if self.length > 50 else 0.5

                    add_message(random.choice(PHRASES_EAT_EGG), self.x, self.y, probability=prob)

                    return True

        return False

    def draw(self, screen):

        if self.sprite is None:

            return

        rotated = pygame.transform.rotate(self.sprite, -math.degrees(self.angle))

        rect = rotated.get_rect(center=(self.x, self.y))

        tint = self.get_tint()

        if tint[3] > 0:

            tint_surf = pygame.Surface(rotated.get_size(), pygame.SRCALPHA)

            tint_surf.fill(tint)

            rotated.blit(tint_surf, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)

        screen.blit(rotated, rect)

def draw_help_panel(screen):

    panel_x = WIDTH - 260

    panel_y = 20

    panel_w = 240

    panel_h = 300

    s = pygame.Surface((panel_w, panel_h), pygame.SRCALPHA)

    s.fill((0, 0, 0, 80))

    screen.blit(s, (panel_x, panel_y))

    pygame.draw.rect(screen, WHITE, (panel_x, panel_y, panel_w, panel_h), 1)

    lines = [

        ("Управление:", WHITE),

        ("Щ - создать щуку", YELLOW),

        ("1 - установить приманку", YELLOW),

        ("Стрелки - управлять приманкой", YELLOW),

        ("И - создать икру", YELLOW),

        ("З - зима/лето", YELLOW),

        ("Выделение мышью - поймать", YELLOW),

        ("существ в области", YELLOW),

    ]

    y_offset = panel_y + 10

    for text, color in lines:

        surf = help_font.render(text, True, color)

        screen.blit(surf, (panel_x + 10, y_offset))

        y_offset += 22

leeches = []

for _ in range(100):

    x, y = random_leech_position()

    leeches.append(Leech(x, y))

pikes = []

eggs = []

bait = None

last_spawn_time = pygame.time.get_ticks()

spawn_interval = 10000

last_reproduction_time = pygame.time.get_ticks()

reproduction_interval = 15000

catch_message = ""

catch_message_timer = 0

selecting = False

select_start = None

select_end = None

grid_rect = None

grid_timer = 0

running = True

while running:

    current_time = pygame.time.get_ticks()

    keys = pygame.key.get_pressed()

    for event in pygame.event.get():

        if event.type == pygame.QUIT:

            running = False

        if event.type == pygame.KEYDOWN:

            if event.unicode == 'щ':

                x, y = random_water_position(small=True)

                pikes.append(Pike(x, y))

            if event.key == pygame.K_ESCAPE:

                running = False

            if event.key == pygame.K_1:

                mx, my = pygame.mouse.get_pos()

                mx = max(SHORE_WIDTH, min(WIDTH - SHORE_WIDTH, mx))

                my = max(SHORE_WIDTH, min(HEIGHT - SHORE_WIDTH, my))

                bait = Bait(mx, my)

            if event.key == pygame.K_b:

                mx, my = pygame.mouse.get_pos()

                mx = max(SHORE_WIDTH, min(WIDTH - SHORE_WIDTH, mx))

                my = max(SHORE_WIDTH, min(HEIGHT - SHORE_WIDTH, my))

                for _ in range(40):

                    if len(eggs) < MAX_EGGS:

                        angle = random.uniform(0, 2 * math.pi)

                        dist = random.uniform(10, 30)

                        x = mx + dist * math.cos(angle)

                        y = my + dist * math.sin(angle)

                        x = max(SHORE_WIDTH + 5, min(WIDTH - SHORE_WIDTH - 5, x))

                        y = max(SHORE_WIDTH + 5, min(HEIGHT - SHORE_WIDTH - 5, y))

                        eggs.append(Egg(x, y))

            if event.key == pygame.K_p:

                toggle_winter()

        if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:

            selecting = True

            select_start = event.pos

            select_end = event.pos

        if event.type == pygame.MOUSEMOTION and selecting:

            select_end = event.pos

        if event.type == pygame.MOUSEBUTTONUP and event.button == 1 and selecting:

            selecting = False

            x1, y1 = select_start

            x2, y2 = select_end

            left = min(x1, x2)

            right = max(x1, x2)

            top = min(y1, y2)

            bottom = max(y1, y2)

            rect = pygame.Rect(left, top, right - left, bottom - top)

            for pike in pikes[:]:

                if rect.collidepoint(pike.x, pike.y):

                    pikes.remove(pike)

            for egg in eggs[:]:

                if rect.collidepoint(egg.x, egg.y):

                    eggs.remove(egg)

            for leech in leeches[:]:

                if rect.collidepoint(leech.x, leech.y):

                    leeches.remove(leech)

            grid_rect = rect

            grid_timer = current_time + GRID_DISPLAY_TIME

    if not winter_active and current_time - last_spawn_time >= spawn_interval:

        last_spawn_time = current_time

        current_count = len(leeches)

        if current_count < MAX_LEECHES:

            new_count = min(current_count * 3, MAX_LEECHES)

            for _ in range(new_count - current_count):

                x, y = random_leech_position()

                leeches.append(Leech(x, y))

    if not winter_active and current_time - last_reproduction_time >= reproduction_interval:

        last_reproduction_time = current_time

        mature = [p for p in pikes if p.length >= 50 and p.state == "normal"]

        if len(mature) >= 2:

            parent = random.choice(mature)

            spawn_x, spawn_y = parent.x, parent.y

            num_eggs = int(1 * parent.length)

            if len(eggs) + num_eggs <= MAX_EGGS:

                for _ in range(num_eggs):

                    angle = random.uniform(0, 2 * math.pi)

                    dist = random.uniform(10, 30)

                    x = spawn_x + dist * math.cos(angle)

                    y = spawn_y + dist * math.sin(angle)

                    x = max(SHORE_WIDTH + 5, min(WIDTH - SHORE_WIDTH - 5, x))

                    y = max(SHORE_WIDTH + 5, min(HEIGHT - SHORE_WIDTH - 5, y))

                    eggs.append(Egg(x, y))

                add_message(random.choice(PHRASES_SPAWN), parent.x, parent.y, probability=1.0)

            parent.state = "fleeing_from_eggs"

            angle = random.uniform(0, 2 * math.pi)

            target_x = spawn_x + 500 * math.cos(angle)

            target_y = spawn_y + 500 * math.sin(angle)

            target_x = max(SHORE_WIDTH, min(WIDTH - SHORE_WIDTH, target_x))

            target_y = max(SHORE_WIDTH, min(HEIGHT - SHORE_WIDTH, target_y))

            parent.target_point = (target_x, target_y)

            parent.target = None

            add_message(random.choice(PHRASES_FLEE), parent.x, parent.y, probability=1.0)

    for egg in eggs[:]:

        if egg.update(pikes):

            eggs.remove(egg)

    if bait is not None:

        bait.update(keys)

    for leech in leeches:

        leech.update()

    for pike in pikes:

        pike.update(leeches, pikes, eggs, bait, current_time)

    for pike in pikes[:]:

        if pike in pikes:

            result = pike.check_eat(leeches, pikes, eggs, bait, current_time)

            if result == "bait_eaten":

                pikes.remove(pike)

                bait = None

                catch_message = "Рыба выловлена!"

                catch_message_timer = current_time + 3000

    for pike in pikes[:]:

        if pike.state in ("wintering", "post_winter"):

            continue

        if not pike.max_length_reached and pike.length >= 160:

            pike.max_length_reached = True

            pike.time_at_max = current_time

        if pike.max_length_reached and (current_time - pike.time_at_max) > 6000:

            if not pike.has_spawned_on_death:

                pike.has_spawned_on_death = True

                for _ in range(DEATH_SPAWN_EGGS):

                    if len(eggs) < MAX_EGGS:

                        angle = random.uniform(0, 2 * math.pi)

                        dist = random.uniform(10, 30)

                        x = pike.x + dist * math.cos(angle)

                        y = pike.y + dist * math.sin(angle)

                        x = max(SHORE_WIDTH + 5, min(WIDTH - SHORE_WIDTH - 5, x))

                        y = max(SHORE_WIDTH + 5, min(HEIGHT - SHORE_WIDTH - 5, y))

                        eggs.append(Egg(x, y))

            add_message(random.choice(PHRASES_DEATH_OLD), pike.x, pike.y, probability=1.0)

            pikes.remove(pike)

            continue

        if pike.length < 160 and (current_time - pike.last_meal_time) > STARVATION_TIME:

            add_message(random.choice(PHRASES_DEATH_STARVE), pike.x, pike.y, probability=1.0)

            pikes.remove(pike)

    screen.blit(sand_surf, (0, 0))

    screen.blit(water_surf, (0, 0))

    if ice_surf is not None:

        screen.blit(ice_surf, (0, 0))

    for leech in leeches:

        leech.draw(screen)

    for egg in eggs:

        egg.draw(screen)

    if bait is not None:

        bait.draw(screen)

    for pike in pikes:

        pike.draw(screen)

    # Отрисовка панели подсказок (поверх всего)

    draw_help_panel(screen)

    messages = [msg for msg in messages if msg['timer'] > current_time]

    if len(messages) > MAX_MESSAGES:

        messages = messages[-MAX_MESSAGES:]

    for msg in messages:

        text_surf = speech_font.render(msg['text'], True, WHITE)

        outline_surf = speech_font.render(msg['text'], True, BLACK)

        x_pos = msg['x'] - text_surf.get_width() // 2

        y_pos = msg['y'] - 30 - text_surf.get_height() // 2

        for dx, dy in [(-1, -1), (-1, 1), (1, -1), (1, 1)]:

            screen.blit(outline_surf, (x_pos + dx, y_pos + dy))

        screen.blit(text_surf, (x_pos, y_pos))

    if selecting and select_start and select_end:

        x1, y1 = select_start

        x2, y2 = select_end

        left = min(x1, x2)

        right = max(x1, x2)

        top = min(y1, y2)

        bottom = max(y1, y2)

        s = pygame.Surface((right - left, bottom - top), pygame.SRCALPHA)

        s.fill((255, 255, 255, 30))

        screen.blit(s, (left, top))

        pygame.draw.rect(screen, WHITE, (left, top, right - left, bottom - top), 1)

    if grid_rect and current_time < grid_timer:

        left, top, w, h = grid_rect

        cell_size = 4

        for y in range(top, top + h + 1, cell_size):

            if y <= HEIGHT:

                pygame.draw.line(screen, BLACK, (left, y), (left + w, y), 1)

        for x in range(left, left + w + 1, cell_size):

            if x <= WIDTH:

                pygame.draw.line(screen, BLACK, (x, top), (x, top + h), 1)

    if grid_rect and current_time >= grid_timer:

        grid_rect = None

    text = font.render(f"Пиявок: {len(leeches)}  Щук: {len(pikes)}  Икринок: {len(eggs)}", True, WHITE)

    screen.blit(text, (10, 10))

    if catch_message and current_time < catch_message_timer:

        msg_surf = big_font.render(catch_message, True, YELLOW)

        screen.blit(msg_surf, (WIDTH // 2 - msg_surf.get_width() // 2, 50))

    elif catch_message:

        catch_message = ""

    pygame.display.flip()

    clock.tick(FPS)

pygame.quit()

Симулятор эволюции[править]

  • (мне надоело создавать одноимённую игру и вот другая, совсем не историчная версия). Игра ещё не смешная, потом исправлю.
    • Геймплей: создавать новые виды, совершенствовать их и не дать им быть съеденными. Также можно управлять охотой своих хищников и юзать их интеллект (у травоядных он пока бесполезный). (Если что, y = yes, n = no).
    • Цель Игры — набрать максимум очков за turns ходов. (turns можно и нужно выставить), можно создать много хищников и выносить ботов
import random
import sys
turns = int(input("Сколько ходов должна длиться игра(оптимально 3 - 7, меньше точно не нужно, больше - будет слишком долго)"))

# ---------- Классы ----------
class Creature:
    def __init__(self, name="Существо"):
        self.name = name
        self.size = 1
        self.population = 1
        self.intelligence = 1
        self.speed = 1
        self.weapons = 1
        self.armor = 1
        self.camouflage = 1
        self.vision = 1
        self.is_predator = False

    def is_alive(self):
        return self.population > 0

    def apply_effects(self, effects):
        for attr, delta in effects.items():
            if hasattr(self, attr) and attr != 'is_predator':
                current = getattr(self, attr)
                new_val = current + delta
                if attr == 'population':
                    new_val = max(0, min(10, new_val))
                else:
                    new_val = max(1, min(10, new_val))
                setattr(self, attr, new_val)

    def add_population(self, delta):
        new_val = self.population + delta
        new_val = max(0, min(10, new_val))
        self.population = new_val

    def total_score(self):
        return (self.size + self.population + self.intelligence +
                self.speed + self.weapons + self.armor +
                self.camouflage + self.vision)

    def __str__(self):
        status = "Хищник" if self.is_predator else "Травоядное"
        alive = "жив" if self.is_alive() else "ВЫМЕР"
        return (f"{self.name} ({status}, {alive}): "
                f"Размер={self.size}, Популяция={self.population}, "
                f"Интеллект={self.intelligence}, Скорость={self.speed}, "
                f"Вооружение={self.weapons}, Защита={self.armor}, "
                f"Маскировка={self.camouflage}, Обзор={self.vision}")


class Card:
    def __init__(self, name, effects):
        self.name = name
        self.effects = effects

    def apply(self, creature):
        creature.apply_effects(self.effects)

    def __str__(self):
        return self.name


# ---------- Пул карт ----------
def create_card_pool():
    base_pool = [
        Card("Увеличение размера", {"size": 1}),
        Card("Увеличение популяции", {"population": 1}),
        Card("Когти", {"weapons": 2, "speed": -1}),
        Card("Панцирь", {"armor": 3, "speed": -1}),
        Card("Крылья", {"speed": 2, "size": -1}),
        Card("Камуфляж", {"camouflage": 2}),
        Card("Острое зрение", {"vision": 2}),
        Card("Ум", {"intelligence": 2}),
        Card("Рога", {"weapons": 1, "size": 1}),
        Card("Шипы", {"armor": 1, "weapons": 1}),
        Card("Ядовитые зубы", {"weapons": 2, "speed": -1}),
        Card("Толстая кожа", {"armor": 2, "speed": -1}),
    ]
    new_cards = [
        Card("Быстрое размножение", {"population": 2}),
        Card("Укрепление панциря", {"armor": 2, "speed": -1}),
        Card("Развитый мозг", {"intelligence": 2}),
        Card("Острые когти", {"weapons": 2}),
        Card("Длинные ноги", {"speed": 2, "size": -1}),
        Card("Миметический камуфляж", {"camouflage": 3, "population": -1}),
        Card("Эхолокация", {"vision": 2, "intelligence": 1}),
        Card("Ядовитые шипы", {"weapons": 1, "armor": 1}),
        Card("Крепкий скелет", {"size": 1, "armor": 1}),
        Card("Повышенная плодовитость", {"population": 1, "size": -1}),
        Card("Способность к планированию", {"intelligence": 2, "speed": -1}),
        Card("Ночное зрение", {"vision": 2, "speed": -1}),
        Card("Терморегуляция", {"size": 1, "speed": 1}),
        Card("Органы чувств", {"vision": 1, "camouflage": 1}),
        Card("Социальность", {"population": 2, "intelligence": 1}),
        Card("Хищнические инстинкты", {"weapons": 2, "speed": 1}),
        Card("Защитная окраска", {"camouflage": 2, "size": -1}),
        Card("Мускулистость", {"speed": 2, "weapons": 1}),
        Card("Регенерация", {"armor": 2, "population": 1}),
        Card("Симбиоз", {"population": 1, "intelligence": 2}),
        Card("Стратегическое мышление", {"intelligence": 3, "speed": -2}),
        Card("Сверхчувствительность", {"vision": 3, "camouflage": -1}),
        Card("Токсичная кровь", {"weapons": 1, "armor": 2}),
        Card("Колючки", {"armor": 2, "speed": -2}),
        Card("Гипноз", {"intelligence": 2, "vision": 1}),
    ]
    return base_pool + new_cards


def random_card(pool):
    return random.choice(pool)


def generate_cards(pool, count):
    return [random_card(pool) for _ in range(count)]


# ---------- Вспомогательные функции ----------
PARAM_NAMES_RU = {
    "size": "Размер",
    "population": "Популяция",
    "intelligence": "Интеллект",
    "speed": "Скорость",
    "weapons": "Вооружение",
    "armor": "Защита",
    "camouflage": "Маскировка",
    "vision": "Обзор"
}


def print_header():
    print("\n" + "=" * 60)
    print("          ЭВОЛЮЦИЯ: пошаговая игра (хищники!)")
    print("=" * 60)


def print_stats(all_creatures):
    alive = [c for c in all_creatures if c.is_alive()]
    if not alive:
        print("Все существа вымерли!")
        return

    names = [c.name[:15] for c in alive]
    col_width = max(len(name) for name in names) + 2
    col_width = max(col_width, 10)

    params = ["size", "population", "intelligence", "speed",
              "weapons", "armor", "camouflage", "vision"]
    param_names = PARAM_NAMES_RU

    print("\nТекущее состояние (живые виды):")
    header = "Параметр".ljust(col_width)
    for c in alive:
        status = "Х" if c.is_predator else "Т"
        short_name = c.name[:15]
        header += f"| {short_name.ljust(col_width-4)} {status}"
    print(header)
    print("-" * len(header))

    for p in params:
        row = param_names[p].ljust(col_width)
        for c in alive:
            val = str(getattr(c, p))
            row += f"| {val.ljust(col_width-2)}"
        print(row)
    print("-" * len(header))
    row = "Сумма".ljust(col_width)
    for c in alive:
        val = str(c.total_score())
        row += f"| {val.ljust(col_width-2)}"
    print(row)
    print()


def get_player_action(card, creature):
    while True:
        print(f"\nКарта: {card.name}")
        effects_parts = []
        for key, val in card.effects.items():
            ru_name = PARAM_NAMES_RU.get(key, key)
            sign = "+" if val >= 0 else ""
            effects_parts.append(f"{ru_name}: {sign}{val}")
        effects_str = ", ".join(effects_parts)
        print(f"Эффекты: {effects_str}")
        choice = input("Применить? (y/n): ").strip().lower()
        if choice in ('y', 'n'):
            return choice == 'y'
        print("Введите 'y' или 'n'.")


def bot_turn_for_creature(creature, pool, card_count):
    cards = generate_cards(pool, card_count)
    for card in cards:
        if random.random() < 0.5:
            card.apply(creature)
        else:
            creature.add_population(1)


# ---------- Функции для интеллекта ----------
def apply_temporary_debuff(prey, allocation):
    """Временно уменьшает параметры жертвы, возвращает старые значения."""
    old_values = {}
    for attr, amount in allocation.items():
        if attr in ['vision', 'speed', 'weapons', 'armor']:
            old = getattr(prey, attr)
            old_values[attr] = old
            new_val = max(1, old - amount)
            setattr(prey, attr, new_val)
    return old_values


def restore_parameters(prey, old_values):
    """Восстанавливает параметры жертвы."""
    for attr, val in old_values.items():
        setattr(prey, attr, val)


def can_hunt(predator, prey):
    if predator is prey:
        return False
    if not predator.is_alive() or not prey.is_alive():
        return False
    return (predator.vision >= prey.camouflage and
            prey.vision <= predator.camouflage and
            predator.speed > prey.speed and
            predator.weapons >= prey.armor and
            prey.weapons <= predator.armor)


# ---------- Выбор цели для игрока ----------
def choose_prey_for_player(predator, all_creatures, player_creatures, bot_species_list):
    """
    Запрашивает у игрока цель для охоты.
    Возвращает выбранное существо или None, если охота пропущена.
    """
    # Собираем список всех живых существ, кроме хищника
    prey_options = [c for c in all_creatures if c.is_alive() and c is not predator]
    if not prey_options:
        print("Нет доступной добычи.")
        return None

    # Выводим список с нумерацией
    print("\nДоступные цели для охоты:")
    # Сначала боты
    for bot_idx, bot_species in enumerate(bot_species_list, start=1):
        bot_alive = [c for c in bot_species if c.is_alive() and c is not predator]
        if bot_alive:
            print(f"  Бот{bot_idx}:")
            for idx, c in enumerate(bot_alive, start=1):
                print(f"    {bot_idx} {idx} -> {c.name} (поп.{c.population})")
    # Затем игрок (свои виды)
    player_alive = [c for c in player_creatures if c.is_alive() and c is not predator]
    if player_alive:
        print("  Игрок:")
        for idx, c in enumerate(player_alive, start=1):
            print(f"    И {idx} -> {c.name} (поп.{c.population})")

    while True:
        inp = input("Введите 'номер_бота номер_вида' (например, '1 3') или 'И номер_вида' для своего вида, или 0 для пропуска: ").strip()
        if inp == '0':
            return None
        parts = inp.split()
        if len(parts) == 2:
            if parts[0].upper() == 'И':
                try:
                    idx = int(parts[1]) - 1
                    if 0 <= idx < len(player_alive):
                        return player_alive[idx]
                    else:
                        print("Неверный номер вида игрока.")
                except ValueError:
                    print("Введите число после 'И'.")
            else:
                try:
                    bot_num = int(parts[0]) - 1
                    species_idx = int(parts[1]) - 1
                    if 0 <= bot_num < len(bot_species_list):
                        bot_alive = [c for c in bot_species_list[bot_num] if c.is_alive() and c is not predator]
                        if 0 <= species_idx < len(bot_alive):
                            return bot_alive[species_idx]
                        else:
                            print("Неверный номер вида для этого бота.")
                    else:
                        print("Неверный номер бота (допустимо 1..3).")
                except ValueError:
                    print("Введите числа.")
        else:
            print("Введите два значения через пробел.")

# ---------- Решение бота использовать интеллект ----------
def bot_decide_intelligence(predator, prey):
    """
    Бот использует интеллект только если без него охота невозможна,
    а с максимальным ослаблением (используя весь интеллект) становится возможной.
    Возвращает allocation (словарь) или None, если интеллект не используется.
    """
    if predator.intelligence <= 0:
        return None
    # Проверяем, возможна ли охота без ослабления
    if can_hunt(predator, prey):
        return None  # уже можно охотиться, не тратим интеллект
    # Пытаемся ослабить prey настолько, чтобы охота стала возможной
    # Параметры, которые можно ослабить: vision, speed, weapons, armor
    attrs = ['vision', 'speed', 'weapons', 'armor']
    # Создаём копию prey для тестирования
    import copy
    test_prey = copy.copy(prey)  # неглубокое копирование, но нам нужны только числа
    # Применим ослабление по всем параметрам до предела (минимум 1)
    alloc = {}
    for attr in attrs:
        max_dec = getattr(test_prey, attr) - 1
        # используем весь интеллект, но не более max_dec
        dec = min(predator.intelligence, max_dec)
        alloc[attr] = dec
        setattr(test_prey, attr, getattr(test_prey, attr) - dec)
    # Проверяем, стала ли охота возможной
    if can_hunt(predator, test_prey):
        # Теперь нужно распределить реально используемые очки (не превышая интеллект)
        # Мы уже выделили dec, но сумма может быть меньше интеллекта, если не хватило параметров
        # Возвращаем allocation, где сумма <= intel
        # Убедимся, что сумма не превышает intel (может быть меньше)
        total = sum(alloc.values())
        if total > predator.intelligence:
            # уменьшаем пропорционально (но это маловероятно, т.к. мы ограничивали)
            pass
        return alloc
    else:
        return None


# ---------- Фаза охоты (переработана) ----------
def hunting_phase(all_creatures, player_creatures, bot_species_list):
    predators = [c for c in all_creatures if c.is_alive() and c.is_predator]
    random.shuffle(predators)

    for predator in predators:
        is_player = predator in player_creatures

        # Выбор цели
        if is_player:
            prey = choose_prey_for_player(predator, all_creatures, player_creatures, bot_species_list)
            if prey is None:
                # Игрок пропустил охоту – хищник не ест и вымирает
                predator.population = 0
                continue
        else:
            # Бот выбирает случайную живую цель
            alive_prey = [c for c in all_creatures if c.is_alive() and c is not predator]
            if not alive_prey:
                predator.population = 0
                continue
            prey = random.choice(alive_prey)

        # Решение использовать интеллект
        use_intel = False
        allocation = {}
        if is_player:
            # Игрок решает сам
            choice = input(f"Использовать интеллект ({predator.intelligence}) для ослабления {prey.name}? (y/n): ").strip().lower()
            if choice == 'y' and predator.intelligence > 0:
                use_intel = True
                # Запрос распределения
                max_intel = predator.intelligence
                print(f"Интеллект: {max_intel}. Распределите очки по параметрам (обзор, скорость, вооружение, защита).")
                while True:
                    try:
                        inp = input("Введите четыре числа через пробел (сумма <= {}): ".format(max_intel)).strip().split()
                        if len(inp) != 4:
                            print("Нужно ровно четыре числа.")
                            continue
                        nums = [int(x) for x in inp]
                        if sum(nums) > max_intel:
                            print(f"Сумма {sum(nums)} превышает интеллект {max_intel}.")
                            continue
                        if any(n < 0 for n in nums):
                            print("Числа должны быть неотрицательными.")
                            continue
                        allocation = {'vision': nums[0], 'speed': nums[1], 'weapons': nums[2], 'armor': nums[3]}
                        break
                    except ValueError:
                        print("Введите целые числа.")
        else:
            # Бот решает использовать интеллект только если это даёт возможность охоты
            alloc = bot_decide_intelligence(predator, prey)
            if alloc is not None:
                use_intel = True
                allocation = alloc
                print(f"{predator.name} использует интеллект для ослабления {prey.name}.")

        # Применяем ослабление
        old_values = {}
        if use_intel and allocation:
            old_values = apply_temporary_debuff(prey, allocation)
            print(f"Ослабление применено: {prey.name} параметры уменьшены.")

        # Охота (до 3 попыток)
        successful_hunts = 0
        for _ in range(3):
            if not prey.is_alive():
                break
            if can_hunt(predator, prey):
                prey.add_population(-1)
                predator.add_population(1)
                successful_hunts += 1
                if successful_hunts == 3:
                    break

        # Восстанавливаем параметры жертвы
        if old_values:
            restore_parameters(prey, old_values)

        # Если не удалось съесть ни разу – вымирает
        if successful_hunts == 0:
            predator.population = 0


def grow_herbivores(all_creatures):
    for c in all_creatures:
        if c.is_alive() and not c.is_predator:
            c.add_population(1)


# ---------- Смена статуса ----------
def player_switch_predator(creature, turn, all_creatures):
    if turn <= 1 or not creature.is_alive():
        return
    while True:
        print(f"\n{creature.name}: сейчас вы {'хищник' if creature.is_predator else 'травоядное'}.")
        choice = input("Хотите сменить статус? (y/n): ").strip().lower()
        if choice == 'n':
            break
        elif choice == 'y':
            if creature.is_predator:
                creature.is_predator = False
                print("Теперь травоядное.")
                break
            else:
                # Игрок может стать хищником без ограничений
                creature.is_predator = True
                print("Теперь хищник!")
                break
        else:
            print("Введите 'y' или 'n'.")


def bot_switch_predator(creature, turn, all_creatures):
    if turn <= 1 or not creature.is_alive():
        return
    if random.random() < 0.5:
        if not creature.is_predator:
            # Бот может стать хищником только если есть добыча
            if has_any_prey(creature, all_creatures):
                creature.is_predator = True
        else:
            creature.is_predator = False


def has_any_prey(predator, all_creatures):
    for other in all_creatures:
        if other is not predator and other.is_alive():
            if can_hunt(predator, other):
                return True
    return False


# ---------- Создание нового вида ----------
def create_new_species_for_player(player_creatures):
    candidates = [c for c in player_creatures if c.is_alive() and c.population >= 2]
    if not candidates:
        print("Нет видов с популяцией ≥2, нельзя создать новый вид.")
        return False

    print("\n--- Создание нового вида (игрок) ---")
    for i, c in enumerate(candidates):
        print(f"{i+1}. {c.name} (популяция: {c.population})")

    while True:
        try:
            choice = input(f"Выберите родительский вид (1-{len(candidates)}) или 0 для отмены: ").strip()
            if choice == '0':
                return False
            idx = int(choice) - 1
            if 0 <= idx < len(candidates):
                parent = candidates[idx]
                break
            else:
                print("Неверный номер.")
        except ValueError:
            print("Введите число.")

    parent.add_population(-2)
    child = Creature(f"Игрок, вид{len(player_creatures)+1}")
    for attr in ['size', 'intelligence', 'speed', 'weapons', 'armor', 'camouflage', 'vision', 'is_predator']:
        setattr(child, attr, getattr(parent, attr))
    child.population = 1
    player_creatures.append(child)
    print(f"Создан новый вид: {child.name} (популяция 1, параметры унаследованы от {parent.name})")
    return True


def create_new_species_for_bot(bot_species, bot_name):
    candidates = [c for c in bot_species if c.is_alive() and c.population >= 2]
    if not candidates:
        return False
    parent = random.choice(candidates)
    parent.add_population(-2)
    child = Creature(f"{bot_name}, вид{len(bot_species)+1}")
    for attr in ['size', 'intelligence', 'speed', 'weapons', 'armor', 'camouflage', 'vision', 'is_predator']:
        setattr(child, attr, getattr(parent, attr))
    child.population = 1
    bot_species.append(child)
    return True


def remove_dead(species_list):
    species_list[:] = [c for c in species_list if c.is_alive()]


# ---------- Основная игра ----------
def main():
    random.seed()

    player_creatures = [Creature("Игрок, вид1")]
    bot_species_list = [
        [Creature("Бот1, вид1")],
        [Creature("Бот2, вид1")],
        [Creature("Бот3, вид1")]
    ]
    bot_split_counters = [random.randint(1, 4) for _ in range(3)]

    card_pool = create_card_pool()

    print_header()
    print(f"Игра будет длиться {turns} ходов.")
    print("Цель: набрать максимальную сумму очков (суммируются все ваши виды).")
    print("В начале каждого хода можно создать новый вид, пожертвовав 2 популяции.")
    print("Боты тоже могут создавать новые виды (примерно раз в 2-5 ходов).")
    print("Мёртвые виды автоматически удаляются.")
    print("Добавлена механика интеллекта: хищник может ослабить жертву перед охотой.")
    print("Игрок может стать хищником без ограничений (со 2-го хода).")
    print("Охота полностью контролируется игроком – выбор цели перед каждой атакой.")
    input("Нажмите Enter, чтобы начать...")

    for turn in range(1, turns + 1):
        print(f"\n--- Ход {turn} ---")

        # 0. Создание новых видов у ботов
        for i, bot_species in enumerate(bot_species_list):
            bot_split_counters[i] -= 1
            if bot_split_counters[i] <= 0:
                success = create_new_species_for_bot(bot_species, f"Бот{i+1}")
                if success:
                    print(f"Бот{i+1} создал новый вид!")
                bot_split_counters[i] = random.randint(2, 5)

        # 1. Создание нового вида у игрока
        create_new_species_for_player(player_creatures)

        # Собираем всех существ
        all_creatures = player_creatures + [c for bot in bot_species_list for c in bot]

        # 2. Смена статуса для игрока
        for creature in player_creatures:
            if creature.is_alive():
                player_switch_predator(creature, turn, all_creatures)

        # Смена статуса для ботов
        for bot_species in bot_species_list:
            for creature in bot_species:
                if creature.is_alive():
                    bot_switch_predator(creature, turn, all_creatures)

        # 3. Показываем состояние
        print_stats(all_creatures)

        # 4. Ход каждого вида игрока
        for creature in player_creatures:
            if not creature.is_alive():
                continue
            print(f"\n--- Ход вида {creature.name} ---")
            cards_for_player = random.randint(2, 5)
            player_cards = generate_cards(card_pool, cards_for_player)
            print(f"Вы получили {len(player_cards)} карт для {creature.name}:")

            for i, card in enumerate(player_cards, 1):
                print(f"\nКарта {i}/{len(player_cards)}:")
                if get_player_action(card, creature):
                    card.apply(creature)
                    print("Карта применена.")
                else:
                    print("Карта пропущена.")
                    creature.add_population(1)

        # 5. Ходы ботов
        for bot_species in bot_species_list:
            for creature in bot_species:
                if not creature.is_alive():
                    continue
                card_count = random.randint(2, 5)
                bot_turn_for_creature(creature, card_pool, card_count)

        # 6. Обновляем список всех существ (после карт могли появиться новые виды у ботов и игрока)
        all_creatures = player_creatures + [c for bot in bot_species_list for c in bot]

        # 7. Выводим обновлённую таблицу перед охотой
        print("\n--- Состояние перед охотой ---")
        print_stats(all_creatures)

        # 8. Фаза охоты (с новой логикой управления)
        print("\n--- Фаза охоты ---")
        hunting_phase(all_creatures, player_creatures, bot_species_list)

        # Проверяем, жив ли хоть один вид игрока
        player_alive = any(c.is_alive() for c in player_creatures)
        if not player_alive:
            print("\nВсе ваши виды вымерли! Игра окончена.")
            sys.exit(0)

        # 9. Прирост травоядных
        grow_herbivores(all_creatures)

        # 10. Удаляем мёртвых из всех списков
        remove_dead(player_creatures)
        for bot_species in bot_species_list:
            remove_dead(bot_species)

        print(f"\n--- Конец хода {turn} ---")
        input("Нажмите Enter для следующего хода...")

    # Финальная статистика
    print("\n" + "=" * 60)
    print("ИГРА ОКОНЧЕНА!")
    print("=" * 60)

    all_creatures = player_creatures + [c for bot in bot_species_list for c in bot]
    print_stats(all_creatures)

    player_score = sum(c.total_score() for c in player_creatures)
    bot_scores = []
    for i, bot_species in enumerate(bot_species_list):
        score = sum(c.total_score() for c in bot_species)
        bot_scores.append((f"Бот{i+1}", score))

    print(f"\nИтоговые суммы очков:")
    print(f"Игрок: {player_score}")
    for name, score in bot_scores:
        print(f"{name}: {score}")

    all_participants = [("Игрок", player_score)] + bot_scores
    max_score = max(score for _, score in all_participants)
    winners = [name for name, score in all_participants if score == max_score]

    if len(winners) == 1:
        print(f"\nПобедитель: {winners[0]} с суммой {max_score} очков!")
        if winners[0] == "Игрок":
            print("Поздравляем!")
        else:
            print("Попробуйте снова!")
    else:
        print(f"\nНичья между {', '.join(winners)} с суммой {max_score} очков!")


if __name__ == "__main__":
    main()