547 lines
25 KiB
Python
547 lines
25 KiB
Python
"""
|
||
🏝️ ОСТРОВ ДВУХ СЕРДЕЦ — визуальная новелла
|
||
Запуск: pip install pygame && python island_novel.py
|
||
"""
|
||
|
||
import pygame
|
||
import sys
|
||
import math
|
||
import random
|
||
|
||
pygame.init()
|
||
|
||
W, H = 1024, 600
|
||
screen = pygame.display.set_mode((W, H), pygame.SCALED | pygame.RESIZABLE)
|
||
pygame.display.set_caption("🏝️ Остров двух сердец")
|
||
clock = pygame.time.Clock()
|
||
|
||
# ── Шрифты ──────────────────────────────────────────────────────────────────
|
||
def load_font(size, bold=False):
|
||
for name in ["Segoe UI", "Arial Unicode MS", "DejaVu Sans", "FreeSans", "Liberation Sans"]:
|
||
try:
|
||
return pygame.font.SysFont(name, size, bold=bold)
|
||
except:
|
||
pass
|
||
return pygame.font.Font(None, size)
|
||
|
||
FONT_BIG = load_font(30, bold=True)
|
||
FONT_MED = load_font(22)
|
||
FONT_SMALL = load_font(18)
|
||
FONT_NAME = load_font(24, bold=True)
|
||
|
||
# ── Цвета ────────────────────────────────────────────────────────────────────
|
||
SKY_DAWN = [(255,180,100), (255,120,80), (180,80,120)]
|
||
SKY_DAY = [(100,180,255), (50,130,230), (30,80,180)]
|
||
SKY_DUSK = [(255,120,60), (200,80,120), (100,40,100)]
|
||
SKY_NIGHT = [(10,10,40), (20,20,80), (5,5,20)]
|
||
|
||
SAND_COL = (240, 220, 160)
|
||
WATER_COL = (60, 160, 220)
|
||
PALM_TRUNK = (120, 80, 40)
|
||
PALM_LEAF = (40, 160, 60)
|
||
|
||
# ── Рисование фонов ──────────────────────────────────────────────────────────
|
||
def draw_gradient(surface, colors, rect=None):
|
||
if rect is None:
|
||
rect = surface.get_rect()
|
||
x, y, w, h = rect
|
||
c1, c2, c3 = colors
|
||
for i in range(h):
|
||
t = i / h
|
||
if t < 0.5:
|
||
r = c1[0] + (c2[0]-c1[0]) * t*2
|
||
g = c1[1] + (c2[1]-c1[1]) * t*2
|
||
b = c1[2] + (c2[2]-c1[2]) * t*2
|
||
else:
|
||
r = c2[0] + (c3[0]-c2[0]) * (t-0.5)*2
|
||
g = c2[1] + (c3[1]-c2[1]) * (t-0.5)*2
|
||
b = c2[2] + (c3[2]-c2[2]) * (t-0.5)*2
|
||
pygame.draw.line(surface, (int(r),int(g),int(b)), (x,y+i),(x+w,y+i))
|
||
|
||
def draw_stars(surface, n=80):
|
||
rng = random.Random(42)
|
||
for _ in range(n):
|
||
x = rng.randint(0, W)
|
||
y = rng.randint(0, H//2)
|
||
r = rng.randint(1,2)
|
||
pygame.draw.circle(surface, (255,255,220), (x,y), r)
|
||
|
||
def draw_sun(surface, time_of_day):
|
||
if time_of_day == "morning":
|
||
pos, col = (150, 120), (255,230,100)
|
||
elif time_of_day == "day":
|
||
pos, col = (512, 80), (255,255,180)
|
||
elif time_of_day == "evening":
|
||
pos, col = (870, 130), (255,160,60)
|
||
else:
|
||
pos, col = None, None
|
||
if pos:
|
||
pygame.draw.circle(surface, col, pos, 45)
|
||
pygame.draw.circle(surface, (255,255,255,80), pos, 55, 3)
|
||
|
||
def draw_moon(surface):
|
||
pygame.draw.circle(surface, (230,230,200), (800,100), 35)
|
||
pygame.draw.circle(surface, (10,10,40), (815,90), 30)
|
||
|
||
def draw_palm(surface, x, y, seed=0):
|
||
rng = random.Random(seed)
|
||
# ствол
|
||
points = [(x,y),(x-8,y-40),(x-5,y-90),(x,y-150),(x+5,y-180)]
|
||
pygame.draw.lines(surface, PALM_TRUNK, False, points, 10)
|
||
# листья
|
||
for angle in range(0, 360, 60):
|
||
rad = math.radians(angle + rng.randint(-20,20))
|
||
lx = x + int(80*math.cos(rad))
|
||
ly = y - 185 + int(50*math.sin(rad))
|
||
pygame.draw.line(surface, PALM_LEAF, (x, y-185), (lx,ly), 6)
|
||
# скругление
|
||
pygame.draw.circle(surface, PALM_LEAF, (lx,ly), 8)
|
||
|
||
def draw_waves(surface, t):
|
||
for i in range(5):
|
||
oy = 30*i
|
||
for x in range(0, W, 4):
|
||
y = int(H*0.62 + 8 + oy + 6*math.sin((x/60 + t*0.5 + i)*1.2))
|
||
pygame.draw.circle(surface, (100,200,240,120), (x,y), 2)
|
||
|
||
def build_background(time_of_day):
|
||
surf = pygame.Surface((W,H))
|
||
# небо
|
||
sky = {"morning":SKY_DAWN,"day":SKY_DAY,"evening":SKY_DUSK,"night":SKY_NIGHT}[time_of_day]
|
||
draw_gradient(surf, sky)
|
||
if time_of_day == "night":
|
||
draw_stars(surf)
|
||
draw_moon(surf)
|
||
else:
|
||
draw_sun(surf, time_of_day)
|
||
# вода
|
||
pygame.draw.rect(surf, WATER_COL, (0, int(H*0.6), W, int(H*0.4)))
|
||
# песок
|
||
pygame.draw.ellipse(surf, SAND_COL, (50, int(H*0.55), W-100, int(H*0.35)))
|
||
# пальмы
|
||
draw_palm(surf, 120, int(H*0.75), seed=1)
|
||
draw_palm(surf, W-180, int(H*0.73), seed=2)
|
||
draw_palm(surf, 400, int(H*0.78), seed=3)
|
||
return surf
|
||
|
||
# Кэшируем фоны
|
||
BG = {tod: build_background(tod) for tod in ["morning","day","evening","night"]}
|
||
|
||
# ── Персонажи ────────────────────────────────────────────────────────────────
|
||
def draw_anime_girl(surface, x, y, palette, name, emotion="neutral", flip=False):
|
||
"""Рисует простую анимешную девочку программно."""
|
||
s = pygame.Surface((160,300), pygame.SRCALPHA)
|
||
|
||
hair_col, dress_col, skin_col, eye_col = palette
|
||
|
||
# тело / платье
|
||
pygame.draw.ellipse(s, dress_col, (35,130,90,140))
|
||
# руки
|
||
pygame.draw.ellipse(s, skin_col, (10,140,30,70))
|
||
pygame.draw.ellipse(s, skin_col, (120,140,30,70))
|
||
# шея
|
||
pygame.draw.rect(s, skin_col, (67,105,26,35))
|
||
# голова
|
||
pygame.draw.ellipse(s, skin_col, (30,20,100,100))
|
||
# волосы
|
||
pygame.draw.ellipse(s, hair_col, (25,10,110,70))
|
||
pygame.draw.ellipse(s, hair_col, (15,30,40,80)) # левая прядь
|
||
pygame.draw.ellipse(s, hair_col, (105,30,40,80)) # правая прядь
|
||
pygame.draw.rect(s, hair_col, (25,10,110,40))
|
||
# глаза
|
||
eye_y = 65
|
||
for ex in [55,95]:
|
||
pygame.draw.ellipse(s, (255,255,255), (ex-12,eye_y,24,16))
|
||
pygame.draw.ellipse(s, eye_col, (ex-8,eye_y+2,16,12))
|
||
pygame.draw.circle(s, (0,0,0), (ex,eye_y+8), 5)
|
||
pygame.draw.circle(s, (255,255,255), (ex+3,eye_y+4), 2)
|
||
# брови
|
||
brow_y = eye_y - 8
|
||
if emotion == "happy":
|
||
pygame.draw.arc(s, hair_col, (45,brow_y,20,10), 0, math.pi, 2)
|
||
pygame.draw.arc(s, hair_col, (90,brow_y,20,10), 0, math.pi, 2)
|
||
elif emotion == "sad":
|
||
pygame.draw.arc(s, hair_col, (45,brow_y,20,10), math.pi, 2*math.pi, 2)
|
||
pygame.draw.arc(s, hair_col, (90,brow_y,20,10), math.pi, 2*math.pi, 2)
|
||
else:
|
||
pygame.draw.line(s, hair_col, (45,brow_y),(65,brow_y+2), 2)
|
||
pygame.draw.line(s, hair_col, (90,brow_y),(110,brow_y+2), 2)
|
||
# рот
|
||
mouth_y = 95
|
||
if emotion == "happy":
|
||
pygame.draw.arc(s, (200,80,80), (60,mouth_y,30,16), math.pi, 2*math.pi, 2)
|
||
elif emotion == "sad":
|
||
pygame.draw.arc(s, (200,80,80), (60,mouth_y,30,16), 0, math.pi, 2)
|
||
else:
|
||
pygame.draw.line(s, (200,80,80), (65,mouth_y+8),(90,mouth_y+8), 2)
|
||
# ушки
|
||
pygame.draw.circle(s, skin_col, (30,75), 10)
|
||
pygame.draw.circle(s, skin_col, (130,75), 10)
|
||
# ноги
|
||
pygame.draw.rect(s, skin_col, (55,255,20,40))
|
||
pygame.draw.rect(s, skin_col, (90,255,20,40))
|
||
|
||
if flip:
|
||
s = pygame.transform.flip(s, True, False)
|
||
surface.blit(s, (x-80, y-280))
|
||
|
||
# Палитры персонажей
|
||
SAKURA_PALETTE = (
|
||
(255,150,180), # розовые волосы
|
||
(220,160,220), # фиолетовое платье
|
||
(255,220,195), # кожа
|
||
(255,100,180), # розовые глаза
|
||
)
|
||
YUKI_PALETTE = (
|
||
(100,180,255), # голубые волосы
|
||
(180,220,255), # голубое платье
|
||
(255,220,195),
|
||
(60,140,255), # синие глаза
|
||
)
|
||
|
||
# ── Диалоги ──────────────────────────────────────────────────────────────────
|
||
STORY = [
|
||
# (day, time_of_day, speaker, emotion, text, points_sakura, points_yuki)
|
||
# ДЕНЬ 1
|
||
("День 1","morning","narrator","neutral",
|
||
"Волны выбросили тебя на песчаный берег. Рядом — две незнакомки.",0,0),
|
||
("День 1","morning","Сакура","happy",
|
||
"Эй! Ты жив? Я Сакура! Не переживай, я умею разводить костёр!",0,0),
|
||
("День 1","morning","Юки","neutral",
|
||
"Я Юки. Надо составить план выживания. Паниковать бесполезно.",0,0),
|
||
("День 1","morning","narrator","neutral",
|
||
"Ты решаешь, кому помочь первой: собрать дрова или найти пресную воду.",0,0),
|
||
# выбор
|
||
("День 1","morning","CHOICE","neutral","[1] Помочь Сакуре с костром / [2] Помочь Юки с водой",0,0),
|
||
|
||
("День 1","day","narrator","neutral",
|
||
"Полдень. Солнце жжёт нещадно. Вы нашли кокосы.",0,0),
|
||
("День 1","day","Сакура","happy",
|
||
"УРА! Кокосы! Давай устроим пикник прямо здесь! Я так рада!",0,0),
|
||
("День 1","day","Юки","neutral",
|
||
"Кокосовое молоко восполнит электролиты. Рационально разделим поровну.",0,0),
|
||
("День 1","day","CHOICE","neutral","[1] Потанцевать с Сакурой / [2] Вместе составить карту острова с Юки",0,0),
|
||
|
||
("День 1","evening","narrator","neutral",
|
||
"Вечер. Костёр потрескивает. Звёзды зажигаются одна за другой.",0,0),
|
||
("День 1","evening","Сакура","happy",
|
||
"Смотри, как красиво! Знаешь... я рада, что ты рядом.",0,0),
|
||
("День 1","evening","Юки","neutral",
|
||
"По созвездиям можно определить стороны света. Это пригодится.",0,0),
|
||
("День 1","evening","CHOICE","neutral","[1] Смотреть на звёзды с Сакурой / [2] Учиться навигации у Юки",0,0),
|
||
|
||
("День 1","night","narrator","neutral",
|
||
"Ночь. Все устали. Ты засыпаешь под шум волн...",0,0),
|
||
|
||
# ДЕНЬ 2
|
||
("День 2","morning","narrator","neutral",
|
||
"Утро второго дня. Тебя будит Сакура — она уже приготовила фрукты!",0,0),
|
||
("День 2","morning","Сакура","happy",
|
||
"Доброе утро! Я нашла манго и бананы! Ешь, пожалуйста!",0,0),
|
||
("День 2","morning","Юки","neutral",
|
||
"Я слышала шум вертолёта ночью. Нужно сделать сигнальный знак на пляже.",0,0),
|
||
("День 2","morning","CHOICE","neutral","[1] Помочь Сакуре украсить лагерь / [2] Помочь Юки сделать SOS-знак",0,0),
|
||
|
||
("День 2","day","narrator","neutral",
|
||
"День. Начался дождь. Пришлось укрываться вместе в пальмовом шалаше.",0,0),
|
||
("День 2","day","Сакура","happy",
|
||
"Ахаха, так тесно! Это как приключение в аниме! Мне нравится!",0,0),
|
||
("День 2","day","Юки","neutral",
|
||
"Дождевую воду можно собирать. Я уже поставила листья как воронки.",0,0),
|
||
("День 2","day","CHOICE","neutral","[1] Рассказать Сакуре смешную историю / [2] Помочь Юки с системой сбора воды",0,0),
|
||
|
||
("День 2","evening","narrator","neutral",
|
||
"Вечер. Дождь стих. Все вышли на берег смотреть на закат.",0,0),
|
||
("День 2","evening","Сакура","happy",
|
||
"Правда ведь... я хотела бы остаться здесь чуть подольше...",0,0),
|
||
("День 2","evening","Юки","neutral",
|
||
"Завтра мы должны попробовать подать сигнал. Но сегодня... спасибо, что ты здесь.",0,0),
|
||
("День 2","evening","CHOICE","neutral","[1] Взять Сакуру за руку / [2] Сесть рядом с Юки",0,0),
|
||
|
||
("День 2","night","narrator","neutral",
|
||
"Ночь. Ты долго смотришь в звёздное небо, думая о них обеих...",0,0),
|
||
|
||
# ДЕНЬ 3
|
||
("День 3","morning","narrator","neutral",
|
||
"Третье утро. Вдали виден силуэт корабля!",0,0),
|
||
("День 3","morning","Юки","happy",
|
||
"Корабль! Мой сигнальный знак сработал! Нас спасут!",0,0),
|
||
("День 3","morning","Сакура","sad",
|
||
"Ура... хотя мне немного грустно. Это были лучшие дни в моей жизни.",0,0),
|
||
("День 3","morning","CHOICE","neutral","[1] Обнять Сакуру / [2] Сказать Юки, что она умница",0,0),
|
||
|
||
("День 3","day","narrator","neutral",
|
||
"Корабль приближается. Осталось несколько часов. Вы готовите плот.",0,0),
|
||
("День 3","day","Сакура","neutral",
|
||
"Эй... что будет, когда мы вернёмся? Мы ведь увидимся снова?",0,0),
|
||
("День 3","day","Юки","neutral",
|
||
"Я хочу сказать тебе кое-что важное, пока есть время...",0,0),
|
||
("День 3","day","CHOICE","neutral","[1] Пообещать Сакуре встретиться / [2] Выслушать Юки",0,0),
|
||
|
||
("День 3","evening","narrator","neutral",
|
||
"Корабль у берега. Это последний вечер на острове.",0,0),
|
||
("День 3","evening","CHOICE","neutral",
|
||
"[1] Попрощаться со всеми / [2] Остаться с Сакурой / [3] Остаться с Юки",0,0),
|
||
]
|
||
|
||
# Очки за каждый выбор: (delta_sakura, delta_yuki)
|
||
CHOICE_POINTS = {
|
||
# day1 morning
|
||
0: {1:(3,0), 2:(0,3)},
|
||
# day1 day
|
||
1: {1:(3,0), 2:(0,3)},
|
||
# day1 evening
|
||
2: {1:(3,0), 2:(0,3)},
|
||
# day2 morning
|
||
3: {1:(3,0), 2:(0,3)},
|
||
# day2 day
|
||
4: {1:(3,0), 2:(0,3)},
|
||
# day2 evening
|
||
5: {1:(3,0), 2:(0,3)},
|
||
# day3 morning
|
||
6: {1:(3,0), 2:(0,3)},
|
||
# day3 day
|
||
7: {1:(3,0), 2:(0,3)},
|
||
# day3 evening — финальный выбор
|
||
8: {1:(0,0), 2:(5,0), 3:(0,5)},
|
||
}
|
||
|
||
# ── Текстовая обёртка ─────────────────────────────────────────────────────────
|
||
def wrap_text(text, font, max_w):
|
||
words = text.split()
|
||
lines, line = [], ""
|
||
for w in words:
|
||
test = (line + " " + w).strip()
|
||
if font.size(test)[0] <= max_w:
|
||
line = test
|
||
else:
|
||
if line: lines.append(line)
|
||
line = w
|
||
if line: lines.append(line)
|
||
return lines
|
||
|
||
# ── UI-компоненты ─────────────────────────────────────────────────────────────
|
||
def draw_textbox(surface, speaker, text, palette=None):
|
||
box = pygame.Rect(30, H-170, W-60, 150)
|
||
s = pygame.Surface((box.w, box.h), pygame.SRCALPHA)
|
||
s.fill((0,0,0,180))
|
||
surface.blit(s, (box.x, box.y))
|
||
pygame.draw.rect(surface, (200,200,200), box, 2, border_radius=8)
|
||
|
||
name_col = (255,200,100)
|
||
if speaker == "Сакура":
|
||
name_col = SAKURA_PALETTE[3]
|
||
elif speaker == "Юки":
|
||
name_col = YUKI_PALETTE[3]
|
||
elif speaker == "narrator":
|
||
name_col = (180,180,180)
|
||
|
||
disp_name = "" if speaker in ("narrator","CHOICE") else speaker
|
||
if disp_name:
|
||
ns = FONT_NAME.render(disp_name, True, name_col)
|
||
surface.blit(ns, (box.x+16, box.y+8))
|
||
|
||
lines = wrap_text(text, FONT_MED, box.w-30)
|
||
for i,l in enumerate(lines[:5]):
|
||
ts = FONT_MED.render(l, True, (255,255,255))
|
||
surface.blit(ts, (box.x+16, box.y+38+i*26))
|
||
|
||
def draw_hud(surface, ps, py):
|
||
# полоски очков
|
||
pygame.draw.rect(surface, (0,0,0,120), (10,10,220,60), border_radius=6)
|
||
pygame.draw.rect(surface, SAKURA_PALETTE[3], (80,18, min(ps*8,140),18), border_radius=4)
|
||
pygame.draw.rect(surface, YUKI_PALETTE[3], (80,42, min(py*8,140),18), border_radius=4)
|
||
surface.blit(FONT_SMALL.render(f"Сакура {ps:2d}", True, SAKURA_PALETTE[3]), (12,18))
|
||
surface.blit(FONT_SMALL.render(f"Юки {py:2d}", True, YUKI_PALETTE[3]), (12,42))
|
||
|
||
def draw_choice_buttons(surface, choices, hover):
|
||
bw, bh = 360, 44
|
||
start_y = H - 170 + 50
|
||
rects = []
|
||
for i, ch in enumerate(choices):
|
||
bx = W//2 - bw//2
|
||
by = start_y + i*(bh+10)
|
||
col = (80,130,200) if hover==i else (40,60,100)
|
||
pygame.draw.rect(surface, col, (bx,by,bw,bh), border_radius=8)
|
||
pygame.draw.rect(surface, (200,200,220),(bx,by,bw,bh), 2, border_radius=8)
|
||
ts = FONT_MED.render(ch, True, (255,255,255))
|
||
surface.blit(ts, (bx + bw//2 - ts.get_width()//2, by+bh//2-ts.get_height()//2))
|
||
rects.append(pygame.Rect(bx,by,bw,bh))
|
||
return rects
|
||
|
||
# ── Экран концовки ────────────────────────────────────────────────────────────
|
||
def ending_screen(surface, ps, py, last_choice):
|
||
surface.blit(BG["day"], (0,0))
|
||
|
||
if last_choice == 1: # уплываем одни
|
||
title = "Конец: Одиночное плавание"
|
||
desc = ("Ты помахал им рукой с борта корабля. Остров скрылся за горизонтом.\n"
|
||
"В душе — тёплые воспоминания о трёх необычных днях.\n"
|
||
"Может, судьба ещё сведёт вас снова...")
|
||
col = (200,200,220)
|
||
elif last_choice == 2 or (ps > py):
|
||
title = "Конец: Сердце Сакуры 🌸"
|
||
desc = ("Сакура прыгнула тебе на шею прямо на пирсе!\n"
|
||
"«Я знала! Я знала, что ты выберешь меня!»\n"
|
||
"Юки улыбнулась и тихо пожелала вам счастья.")
|
||
col = SAKURA_PALETTE[3]
|
||
draw_anime_girl(surface, 300, 420, SAKURA_PALETTE, "Сакура", "happy")
|
||
elif last_choice == 3 or (py > ps):
|
||
title = "Конец: Звезда Юки ❄️"
|
||
desc = ("Юки остановила тебя у трапа и тихо сказала:\n"
|
||
"«Я... хотела бы видеть тебя снова. Очень.»\n"
|
||
"Её щёки стали розовыми. Это было дороже всех слов.")
|
||
col = YUKI_PALETTE[3]
|
||
draw_anime_girl(surface, 700, 420, YUKI_PALETTE, "Юки", "happy", flip=True)
|
||
else:
|
||
title = "Конец: Три сердца"
|
||
desc = ("Вы все трое не смогли расстаться.\n"
|
||
"Решили вместе путешествовать на арендованной яхте.\n"
|
||
"Остров остался позади, а впереди — бесконечное море.")
|
||
col = (255,255,200)
|
||
draw_anime_girl(surface, 280, 420, SAKURA_PALETTE, "Сакура", "happy")
|
||
draw_anime_girl(surface, 720, 420, YUKI_PALETTE, "Юки", "happy", flip=True)
|
||
|
||
overlay = pygame.Surface((W, H), pygame.SRCALPHA)
|
||
overlay.fill((0,0,0,100))
|
||
surface.blit(overlay, (0,0))
|
||
|
||
ts = FONT_BIG.render(title, True, col)
|
||
surface.blit(ts, (W//2 - ts.get_width()//2, 60))
|
||
|
||
for i, line in enumerate(desc.split("\n")):
|
||
ls = FONT_MED.render(line, True, (255,255,255))
|
||
surface.blit(ls, (W//2 - ls.get_width()//2, 150 + i*35))
|
||
|
||
hint = FONT_SMALL.render("Нажми любую клавишу для выхода", True, (180,180,180))
|
||
surface.blit(hint, (W//2 - hint.get_width()//2, H-40))
|
||
|
||
score_s = FONT_SMALL.render(f"Очки: Сакура {ps} | Юки {py}", True, (200,200,200))
|
||
surface.blit(score_s, (W//2 - score_s.get_width()//2, H-65))
|
||
|
||
pygame.display.flip()
|
||
waiting = True
|
||
while waiting:
|
||
for e in pygame.event.get():
|
||
if e.type == pygame.QUIT: pygame.quit(); sys.exit()
|
||
if e.type in (pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN):
|
||
waiting = False
|
||
|
||
# ── Главный цикл ──────────────────────────────────────────────────────────────
|
||
def main():
|
||
ps, py = 0, 0 # очки
|
||
scene_idx = 0
|
||
choice_idx = 0 # счётчик выборов
|
||
wave_t = 0
|
||
hover = -1
|
||
last_final_choice = 1
|
||
|
||
# фильтруем сцены по индексу, собираем выборы
|
||
scene_list = []
|
||
for entry in STORY:
|
||
scene_list.append(entry)
|
||
|
||
current = 0
|
||
choice_mode = False
|
||
choice_options = []
|
||
choice_rects = []
|
||
|
||
def get_bg(tod):
|
||
return BG.get(tod, BG["day"])
|
||
|
||
def parse_choices(text):
|
||
parts = [p.strip() for p in text.split("/")]
|
||
result = []
|
||
for p in parts:
|
||
if p.startswith("["):
|
||
bracket_end = p.index("]")
|
||
result.append(p[bracket_end+1:].strip())
|
||
else:
|
||
result.append(p)
|
||
return result
|
||
|
||
running = True
|
||
while running and current < len(scene_list):
|
||
dt = clock.tick(60) / 1000
|
||
wave_t += dt
|
||
scene = scene_list[current]
|
||
day_label, tod, speaker, emotion, text, _, _ = scene
|
||
|
||
bg = get_bg(tod)
|
||
|
||
# events
|
||
for e in pygame.event.get():
|
||
if e.type == pygame.QUIT:
|
||
pygame.quit(); sys.exit()
|
||
if e.type == pygame.MOUSEMOTION and choice_mode:
|
||
hover = -1
|
||
for i,r in enumerate(choice_rects):
|
||
if r.collidepoint(e.pos): hover = i
|
||
if e.type == pygame.MOUSEBUTTONDOWN and e.button==1:
|
||
if choice_mode:
|
||
for i,r in enumerate(choice_rects):
|
||
if r.collidepoint(e.pos):
|
||
chosen = i+1
|
||
if choice_idx in CHOICE_POINTS:
|
||
dp = CHOICE_POINTS[choice_idx].get(chosen,(0,0))
|
||
ps += dp[0]; py += dp[1]
|
||
# для финального выбора запомним
|
||
if choice_idx == 8:
|
||
last_final_choice = chosen
|
||
choice_idx += 1
|
||
choice_mode = False
|
||
current += 1
|
||
break
|
||
else:
|
||
current += 1
|
||
if e.type == pygame.KEYDOWN:
|
||
if not choice_mode:
|
||
current += 1
|
||
|
||
# draw
|
||
screen.blit(bg, (0,0))
|
||
# анимация волн поверх воды
|
||
draw_waves(screen, wave_t)
|
||
|
||
# персонажи
|
||
if speaker != "narrator" and speaker != "CHOICE":
|
||
if speaker == "Сакура":
|
||
draw_anime_girl(screen, 280, H-175, SAKURA_PALETTE, "Сакура", emotion)
|
||
elif speaker == "Юки":
|
||
draw_anime_girl(screen, W-280, H-175, YUKI_PALETTE, "Юки", emotion, flip=True)
|
||
|
||
# всегда рисуем обоих в фоне (маленьких)
|
||
if speaker == "narrator" or speaker == "CHOICE":
|
||
# рисуем обоих немного прозрачно
|
||
tmp = pygame.Surface((W,H), pygame.SRCALPHA)
|
||
draw_anime_girl(tmp, 220, H-175, SAKURA_PALETTE, "Сакура", "neutral")
|
||
draw_anime_girl(tmp, W-220, H-175, YUKI_PALETTE, "Юки", "neutral", flip=True)
|
||
tmp.set_alpha(120)
|
||
screen.blit(tmp, (0,0))
|
||
|
||
# день-лейбл
|
||
day_surf = FONT_BIG.render(day_label, True, (255,255,220))
|
||
screen.blit(day_surf, (W//2 - day_surf.get_width()//2, 12))
|
||
|
||
# HUD
|
||
draw_hud(screen, ps, py)
|
||
|
||
if speaker == "CHOICE":
|
||
choice_mode = True
|
||
choice_options = parse_choices(text)
|
||
choice_rects = draw_choice_buttons(screen, choice_options, hover)
|
||
else:
|
||
draw_textbox(screen, speaker, text)
|
||
hint = FONT_SMALL.render("[ Кликни или нажми любую клавишу ]", True, (160,160,160))
|
||
screen.blit(hint, (W//2-hint.get_width()//2, H-22))
|
||
choice_mode = False
|
||
|
||
pygame.display.flip()
|
||
|
||
# Концовка
|
||
ending_screen(screen, ps, py, last_final_choice)
|
||
pygame.quit()
|
||
|
||
if __name__ == "__main__":
|
||
main()
|