nice working and testing

This commit is contained in:
sShemet
2026-03-17 00:16:49 +05:00
parent c623b8c2f9
commit f639deac32
36 changed files with 256039 additions and 37759 deletions

6847
clannad_parser/00414.scr.txt Normal file

File diff suppressed because it is too large Load Diff

9314
clannad_parser/00416.scr.txt Normal file

File diff suppressed because it is too large Load Diff

11225
clannad_parser/00417.scr.txt Normal file

File diff suppressed because it is too large Load Diff

10127
clannad_parser/00418.scr.txt Normal file

File diff suppressed because it is too large Load Diff

6947
clannad_parser/00419.scr.txt Normal file

File diff suppressed because it is too large Load Diff

7963
clannad_parser/00420.scr.txt Normal file

File diff suppressed because it is too large Load Diff

10245
clannad_parser/00421.scr.txt Normal file

File diff suppressed because it is too large Load Diff

16973
clannad_parser/00422.scr.txt Normal file

File diff suppressed because it is too large Load Diff

16291
clannad_parser/00423.scr.txt Normal file

File diff suppressed because it is too large Load Diff

14209
clannad_parser/00424.scr.txt Normal file

File diff suppressed because it is too large Load Diff

9637
clannad_parser/00425.scr.txt Normal file

File diff suppressed because it is too large Load Diff

7545
clannad_parser/00429.scr.txt Normal file

File diff suppressed because it is too large Load Diff

12701
clannad_parser/02426.scr.txt Normal file

File diff suppressed because it is too large Load Diff

10268
clannad_parser/02509.scr.txt Normal file

File diff suppressed because it is too large Load Diff

13121
clannad_parser/04425.scr.txt Normal file

File diff suppressed because it is too large Load Diff

8831
clannad_parser/04503.scr.txt Normal file

File diff suppressed because it is too large Load Diff

9506
clannad_parser/04508.scr.txt Normal file

File diff suppressed because it is too large Load Diff

7885
clannad_parser/05424.scr.txt Normal file

File diff suppressed because it is too large Load Diff

10889
clannad_parser/05430.scr.txt Normal file

File diff suppressed because it is too large Load Diff

13128
clannad_parser/07400.scr.txt Normal file

File diff suppressed because it is too large Load Diff

12176
clannad_parser/07401.scr.txt Normal file

File diff suppressed because it is too large Load Diff

14175
clannad_parser/07500.scr.txt Normal file

File diff suppressed because it is too large Load Diff

13041
clannad_parser/07600.scr.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
import sys
import os
def parse_text_command(data, pos):
# Читаем параметры в Little Endian
@@ -17,18 +18,33 @@ def parse_text_command(data, pos):
# Декодируем UTF-16LE текст
text = text_bytes.decode('utf-16le', errors='replace')
total_size = 8 + len(text_bytes) + 2 # 8 байт заголовка + текст + 0000
total_size = 8 + len(text_bytes) # 8 байт заголовка + текст + 0000
return f"TextID: {text_id}, VoiceID: {voice_id}, Text: {text}", total_size
# База команд (идентификаторы в Big Endian)
command_db = {
0x0001: {
"name": "Sprite",
"size": 14,
"handler": lambda data, pos: (f"ID: {int.from_bytes(data[pos+2:pos+4], 'little')} | " \
+ f"MODE: {int.from_bytes(data[pos+4:pos+6], 'little')} | " \
+ f"X: {int.from_bytes(data[pos+6:pos+8], 'big')} | " \
+ f"Y: {int.from_bytes(data[pos+8:pos+10], 'big')} | " \
+ f"fade: {int.from_bytes(data[pos+10:pos+12], 'big')}ms" \
, 12)
},
0x1401: {
"name": "PlayBGM",
"name": "BGM",
"size": 2,
"handler": lambda data, pos: (f"BGM Track: {int.from_bytes(data[pos:pos+2], 'little')}", 2)
},
0x1600: {
"name": "Pause",
"size": 2,
"handler": lambda data, pos: (f"{int.from_bytes(data[pos:pos+2], 'little')}ms", 2)
},
0x0A00: {
"name": "ShowText",
"name": "Text",
"size": 8,
"handler": parse_text_command
}
@@ -55,20 +71,51 @@ def parse_script(file_path):
while pos % 4 != 0:
pos += 1
else:
# output.append(f"[0x{pos:08X}][0x{cmd:04X}] UNKNOWN COMMAND")
output.append(f"[0x{pos:08X}][0x{cmd:04X}] UNKNOWN COMMAND")
pos += 2
# Сохраняем в файл
with open("script" + file_path + ".txt", "w", encoding="utf-8") as f:
output_file = file_path + ".txt"
with open(output_file, "w", encoding="utf-8") as f:
f.write("\n".join(output))
print(f"Обработан: {file_path} -> {output_file}")
return output
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python parser.py <script.bin>")
sys.exit(1)
def batch_process_scr_files():
# Получаем текущий каталог
current_dir = os.getcwd()
results = parse_script(sys.argv[1])
for line in results:
print(line)
# Ищем все файлы с расширением .scr
scr_files = []
for file_name in os.listdir(current_dir):
if file_name.lower().endswith('.scr'):
scr_files.append(file_name)
if not scr_files:
print("Файлы с расширением .scr не найдены в текущем каталоге.")
return []
print(f"Найдено {len(scr_files)} файлов .scr для обработки:")
for file_name in scr_files:
print(f" - {file_name}")
results = []
for file_name in scr_files:
try:
file_results = parse_script(file_name)
results.extend(file_results)
except Exception as e:
print(f"Ошибка при обработке файла {file_name}: {e}")
return results
if __name__ == "__main__":
if len(sys.argv) > 1:
# Если указан аргумент командной строки, обрабатываем указанный файл
results = parse_script(sys.argv[1])
for line in results:
print(line)
else:
# Если аргументов нет, сканируем текущий каталог
batch_process_scr_files()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,8 @@ video_id = y2fwatM1oOg
enabled = 0
[Twitch]
channel = coulthardf1
channel = sergeyshemet
; channel = nuke73
enabled = 1
[Alerts]

View File

@@ -232,11 +232,11 @@ def make_json_object():
# Добавляем приветственное сообщение каждые 10 минут
current_second = int(time.time())
if current_second % 600 <= 5:
if current_second % 1000 <= 5:
# Проверяем, есть ли приветственное сообщение от Eikichi-bot в последних 20 сообщениях
has_recent_hello = any(
comment.get('sendr') == 'Eikichi-bot' and comment.get('type') == 'hello'
for comment in all_comments[-20:] # Последние 20 сообщений
for comment in all_comments[-40:] # Последние 20 сообщений
)
if not has_recent_hello:

View File

@@ -18,7 +18,7 @@ body {
font-family: "Roboto Condensed", sans-serif;
font-size: 1em;
font-size: 1.1em;
letter-spacing: 0.1px;
color: lighter;
text-rendering: optimizeLegibility;
@@ -29,8 +29,9 @@ body {
#chatwin {
/* color: white; */
height: 100%;
min-width: 150px;
max-width: 450px;
width: 100%;
/* min-width: 150px; */
max-width: 510px;
display: flex;
flex-direction: column;
@@ -61,7 +62,7 @@ body {
.nameline {
color: white;
padding: 5px;
width: 25%;
width: 27%;
text-align: center;
display: flex;
justify-content: center;
@@ -70,9 +71,9 @@ body {
flex-direction: column;
vertical-align: auto;
flex: 0 0 25%; /* Фиксируем ширину - не растягивается и не сжимается */
flex: 0 0 27%; /* Фиксируем ширину - не растягивается и не сжимается */
min-width: 0; /* Важно для работы переноса текста в flex-контейнере */
max-width: 25%; /* Ограничиваем максимальную ширину */
max-width: 27%; /* Ограничиваем максимальную ширину */
word-wrap: break-word; /* Перенос длинных слов */
overflow-wrap: break-word; /* Современная версия */
word-break: break-word; /* Разрыв слов если нужно */
@@ -123,7 +124,7 @@ body {
.msgline {
padding: 5px;
width: 75%;
width: 73%;
color: #fff;
flex: 1; /* Занимает всё оставшееся пространство */

390
jrpg/girl.py Normal file
View File

@@ -0,0 +1,390 @@
import pygame
import math
import sys
pygame.init()
W, H = 600, 750
screen = pygame.display.set_mode((W, H))
pygame.display.set_caption("Anime Girl - Blue Pink Black")
# Color palette
BLACK = (5, 5, 15)
DARK = (10, 10, 30)
PINK = (255, 100, 180)
PINK2 = (255, 160, 210)
PINK3 = (200, 60, 130)
BLUE = (80, 180, 255)
BLUE2 = (40, 120, 220)
BLUE3 = (130, 210, 255)
CYAN = (0, 220, 255)
WHITE = (255, 255, 255)
SKIN = (255, 220, 210)
SKIN2 = (240, 195, 185)
PURPLE = (160, 80, 220)
GLITCH = (0, 255, 200)
clock = pygame.time.Clock()
t = 0
def draw_gradient_bg(surf):
for y in range(H):
ratio = y / H
r = int(5 + 15 * ratio)
g = int(5 + 10 * ratio)
b = int(15 + 40 * ratio)
pygame.draw.line(surf, (r, g, b), (0, y), (W, y))
def draw_glow(surf, color, pos, radius, alpha=80):
glow = pygame.Surface((radius*2, radius*2), pygame.SRCALPHA)
for i in range(radius, 0, -4):
a = int(alpha * (i / radius) ** 2)
r, g, b = color
pygame.draw.circle(glow, (r, g, b, a), (radius, radius), i)
surf.blit(glow, (pos[0]-radius, pos[1]-radius))
def draw_stars(surf, t):
import random
random.seed(42)
for i in range(60):
x = random.randint(0, W)
y = random.randint(0, H//2)
size = random.uniform(1, 2.5)
flicker = 0.5 + 0.5 * math.sin(t * 2 + i)
c = int(150 + 105 * flicker)
pygame.draw.circle(surf, (c, c, min(255, c+50)), (x, y), int(size))
def draw_scanlines(surf):
s = pygame.Surface((W, H), pygame.SRCALPHA)
for y in range(0, H, 4):
pygame.draw.line(s, (0, 0, 0, 30), (0, y), (W, y))
surf.blit(s, (0, 0))
def draw_girl(surf, t):
# floating bob
bob = math.sin(t * 1.5) * 5
cx = W // 2
base_y = 480 + bob
# --- HAIR BACK LAYER ---
# Long flowing hair behind body
hair_pts_back = [
(cx - 120, base_y - 320),
(cx - 160, base_y - 200),
(cx - 190, base_y - 50),
(cx - 170, base_y + 100),
(cx - 120, base_y + 200),
(cx - 60, base_y + 280),
(cx, base_y + 300),
(cx + 60, base_y + 280),
(cx + 120, base_y + 200),
(cx + 170, base_y + 100),
(cx + 190, base_y - 50),
(cx + 160, base_y - 200),
(cx + 120, base_y - 320),
]
pygame.draw.polygon(surf, BLUE2, hair_pts_back)
pygame.draw.polygon(surf, (20, 60, 120), hair_pts_back, 2)
# --- BODY / TORSO ---
# Dress / top
body_rect = pygame.Rect(cx - 70, base_y - 100, 140, 200)
pygame.draw.ellipse(surf, DARK, pygame.Rect(cx-65, base_y-95, 130, 180))
# Dress gradient effect - layered
for i, col in enumerate([(30, 10, 60), (50, 20, 90), (70, 30, 110)]):
pygame.draw.ellipse(surf, col,
pygame.Rect(cx-60+i*3, base_y-90+i*2, 120-i*6, 170-i*4))
# Collar / ribbon
ribbon_pts = [(cx-40, base_y-90), (cx, base_y-70), (cx+40, base_y-90),
(cx+20, base_y-60), (cx, base_y-50), (cx-20, base_y-60)]
pygame.draw.polygon(surf, PINK3, ribbon_pts)
# Bow on collar
bow_left = [(cx-35, base_y-78), (cx-8, base_y-65), (cx-8, base_y-75)]
bow_right = [(cx+35, base_y-78), (cx+8, base_y-65), (cx+8, base_y-75)]
pygame.draw.polygon(surf, PINK, bow_left)
pygame.draw.polygon(surf, PINK, bow_right)
pygame.draw.circle(surf, PINK2, (cx, base_y-72), 5)
# Glowing trim
pygame.draw.arc(surf, CYAN,
pygame.Rect(cx-62, base_y-92, 124, 40), 0, math.pi, 2)
# --- NECK ---
pygame.draw.rect(surf, SKIN, pygame.Rect(cx-18, base_y-140, 36, 55), border_radius=8)
# --- HEAD ---
head_cy = base_y - 200
# Face
pygame.draw.ellipse(surf, SKIN, pygame.Rect(cx-65, head_cy-75, 130, 155))
# Cheek blush
blush_alpha = pygame.Surface((60, 25), pygame.SRCALPHA)
pygame.draw.ellipse(blush_alpha, (255, 100, 130, 80), (0, 0, 60, 25))
surf.blit(blush_alpha, (cx-85, head_cy+10))
surf.blit(blush_alpha, (cx+25, head_cy+10))
# --- HAIR FRONT ---
# Top of head / bangs
hair_top = [
(cx-70, head_cy-70),
(cx-90, head_cy-100),
(cx-70, head_cy-130),
(cx-30, head_cy-155),
(cx, head_cy-165),
(cx+30, head_cy-155),
(cx+70, head_cy-130),
(cx+90, head_cy-100),
(cx+70, head_cy-70),
(cx+50, head_cy-40),
(cx, head_cy-30),
(cx-50, head_cy-40),
]
pygame.draw.polygon(surf, BLUE, hair_top)
# Bangs (front)
bang1 = [(cx-70, head_cy-70), (cx-95, head_cy-10), (cx-65, head_cy+20),
(cx-40, head_cy-10), (cx-50, head_cy-50)]
bang2 = [(cx-30, head_cy-75), (cx-45, head_cy-5), (cx-20, head_cy+10),
(cx+5, head_cy-10), (cx-5, head_cy-60)]
bang3 = [(cx+30, head_cy-75), (cx+10, head_cy-5), (cx+30, head_cy+15),
(cx+55, head_cy-5), (cx+45, head_cy-60)]
bang4 = [(cx+70, head_cy-70), (cx+50, head_cy-50), (cx+55, head_cy+20),
(cx+80, head_cy-10), (cx+95, head_cy-30)]
for bang in [bang1, bang2, bang3, bang4]:
pygame.draw.polygon(surf, BLUE, bang)
# Hair highlight
highlight_pts = [(cx-20, head_cy-155), (cx+20, head_cy-155),
(cx+10, head_cy-110), (cx-10, head_cy-110)]
pygame.draw.polygon(surf, BLUE3, highlight_pts)
# Hair shine line
pygame.draw.arc(surf, (200, 240, 255),
pygame.Rect(cx-30, head_cy-140, 60, 30), 0.2, math.pi-0.2, 2)
# --- EYES ---
eye_y = head_cy + 5
eye_offset = math.sin(t * 0.8) * 1 # subtle sway
for side in [-1, 1]:
ex = cx + side * 28
ey = eye_y
# Eye shadow
eye_sh = pygame.Surface((50, 30), pygame.SRCALPHA)
pygame.draw.ellipse(eye_sh, (80, 0, 60, 100), (0, 0, 50, 30))
surf.blit(eye_sh, (ex-25, ey-10))
# White of eye
pygame.draw.ellipse(surf, WHITE, pygame.Rect(ex-18, ey-12, 36, 28))
# Iris - blue gradient effect
pygame.draw.ellipse(surf, BLUE2, pygame.Rect(ex-13, ey-10, 26, 25))
pygame.draw.ellipse(surf, BLUE, pygame.Rect(ex-10, ey-7, 20, 19))
pygame.draw.ellipse(surf, CYAN, pygame.Rect(ex-6, ey-4, 12, 12))
# Pupil
pygame.draw.ellipse(surf, BLACK, pygame.Rect(ex-5, ey-3, 10, 11))
# Eye shine
pygame.draw.circle(surf, WHITE, (ex-4, ey-1), 3)
pygame.draw.circle(surf, WHITE, (ex+3, ey+3), 2)
# Eyelashes - upper lid
pygame.draw.arc(surf, BLACK,
pygame.Rect(ex-19, ey-14, 38, 22), 0.1, math.pi-0.1, 3)
# Lash lines
for lx_off, angle in [(-18, 2.4), (-10, 2.0), (-2, 1.7), (6, 1.5), (14, 1.8)]:
lx = ex + lx_off
pygame.draw.line(surf, BLACK,
(lx, ey - int(12 * math.sin(angle))),
(lx - side*2, ey - int(12 * math.sin(angle)) - 5), 2)
# Lower lash
pygame.draw.arc(surf, SKIN2,
pygame.Rect(ex-16, ey+2, 32, 14), math.pi+0.2, 2*math.pi-0.2, 1)
# --- NOSE (subtle) ---
pygame.draw.arc(surf, SKIN2,
pygame.Rect(cx-6, head_cy+28, 12, 8), math.pi+0.3, 2*math.pi-0.3, 1)
# --- MOUTH ---
mouth_y = head_cy + 50
# Smile
pygame.draw.arc(surf, PINK3,
pygame.Rect(cx-18, mouth_y, 36, 16), math.pi+0.3, 2*math.pi-0.3, 2)
# Lips
pygame.draw.ellipse(surf, PINK2, pygame.Rect(cx-14, mouth_y+1, 28, 9))
pygame.draw.arc(surf, PINK3,
pygame.Rect(cx-12, mouth_y, 24, 12), math.pi+0.4, 2*math.pi-0.4, 1)
# --- EARS ---
for side in [-1, 1]:
ex = cx + side * 63
pygame.draw.ellipse(surf, SKIN, pygame.Rect(ex - 10, head_cy - 5, 20, 28))
pygame.draw.ellipse(surf, SKIN2, pygame.Rect(ex - 6, head_cy, 12, 18))
# --- HAIRPIN / ACCESSORIES ---
# Star hairpin left
star_x, star_y = cx - 50, head_cy - 110
for i in range(5):
a1 = math.radians(i * 72 - 90)
a2 = math.radians(i * 72 + 36 - 90)
p1 = (star_x + 12 * math.cos(a1), star_y + 12 * math.sin(a1))
p2 = (star_x + 5 * math.cos(a2), star_y + 5 * math.sin(a2))
pygame.draw.line(surf, PINK, (int(p1[0]), int(p1[1])),
(int(p2[0]), int(p2[1])), 2)
pygame.draw.circle(surf, PINK, (star_x, star_y), 4)
# Glowing dot accessories in hair
for gx, gy, gc in [(cx+40, head_cy-130, CYAN),
(cx+70, head_cy-90, PINK),
(cx-60, head_cy-95, BLUE3)]:
draw_glow(surf, gc, (gx, gy), 15, 60)
pygame.draw.circle(surf, gc, (gx, gy), 4)
pygame.draw.circle(surf, WHITE, (gx, gy), 2)
# --- ARMS ---
# Left arm
arm_wave = math.sin(t * 1.2) * 8
arm_l_pts = [(cx-70, base_y-80), (cx-100, base_y-30+arm_wave),
(cx-115, base_y+60+arm_wave), (cx-95, base_y+65+arm_wave),
(cx-80, base_y-20+arm_wave), (cx-55, base_y-70)]
pygame.draw.polygon(surf, (40, 20, 80), arm_l_pts)
# Right arm
arm_r_pts = [(cx+70, base_y-80), (cx+100, base_y-30-arm_wave),
(cx+115, base_y+60-arm_wave), (cx+95, base_y+65-arm_wave),
(cx+80, base_y-20-arm_wave), (cx+55, base_y-70)]
pygame.draw.polygon(surf, (40, 20, 80), arm_r_pts)
# Hands
pygame.draw.circle(surf, SKIN, (cx-108, base_y+62+arm_wave), 14)
pygame.draw.circle(surf, SKIN, (cx+108, base_y+62-arm_wave), 14)
# Arm glow trim
pygame.draw.arc(surf, CYAN,
pygame.Rect(cx-118, base_y+46+arm_wave, 20, 20), 0, math.pi*2, 1)
pygame.draw.arc(surf, PINK,
pygame.Rect(cx+98, base_y+46-arm_wave, 20, 20), 0, math.pi*2, 1)
# --- SKIRT ---
skirt_top = base_y + 90
skirt_pts = [
(cx - 70, skirt_top),
(cx - 130, skirt_top + 100),
(cx - 110, skirt_top + 180),
(cx, skirt_top + 200),
(cx + 110, skirt_top + 180),
(cx + 130, skirt_top + 100),
(cx + 70, skirt_top),
]
pygame.draw.polygon(surf, (50, 20, 90), skirt_pts)
# Skirt details - ruffles
for i, col in enumerate([PURPLE, PINK3, PINK]):
offset = i * 15
s_pts = [
(cx - 70 - offset//2, skirt_top + offset*3),
(cx - 130 - offset, skirt_top + 100 + offset*2),
(cx + 130 + offset, skirt_top + 100 + offset*2),
(cx + 70 + offset//2, skirt_top + offset*3),
]
pygame.draw.lines(surf, col, False, s_pts, 2)
# Skirt sparkles
skirt_wave = math.sin(t * 2) * 3
for sx, sy in [(cx-80, skirt_top+40), (cx+60, skirt_top+60),
(cx-30, skirt_top+120), (cx+100, skirt_top+140)]:
pygame.draw.circle(surf, CYAN, (sx, sy+int(skirt_wave)), 2)
pygame.draw.circle(surf, PINK2, (sx+5, sy-5+int(skirt_wave)), 1)
# --- LEGS ---
leg_h = skirt_top + 180
pygame.draw.rect(surf, SKIN, pygame.Rect(cx-40, leg_h, 30, 80), border_radius=8)
pygame.draw.rect(surf, SKIN, pygame.Rect(cx+10, leg_h, 30, 80), border_radius=8)
# Stockings / boots
pygame.draw.rect(surf, (20, 20, 50), pygame.Rect(cx-42, leg_h+50, 34, 40), border_radius=5)
pygame.draw.rect(surf, (20, 20, 50), pygame.Rect(cx+8, leg_h+50, 34, 40), border_radius=5)
pygame.draw.line(surf, BLUE, (cx-42, leg_h+50), (cx-8, leg_h+50), 2)
pygame.draw.line(surf, BLUE, (cx+8, leg_h+50), (cx+42, leg_h+50), 2)
def draw_particles(surf, t):
for i in range(20):
px = int((W/2 + 220 * math.cos(t*0.3 + i * 0.9)) % W)
py = int((300 + 200 * math.sin(t*0.4 + i * 1.1) + i * 20) % H)
size = 1 + int(2 * abs(math.sin(t + i)))
colors = [PINK, CYAN, BLUE3, PINK2]
c = colors[i % len(colors)]
alpha_surf = pygame.Surface((size*4, size*4), pygame.SRCALPHA)
pygame.draw.circle(alpha_surf, (*c, 120), (size*2, size*2), size*2)
surf.blit(alpha_surf, (px-size*2, py-size*2))
pygame.draw.circle(surf, c, (px, py), size)
def draw_frame_decoration(surf, t):
# Corner decorations
corner_glow = abs(math.sin(t)) * 100 + 100
corners = [(20, 20), (W-20, 20), (20, H-20), (W-20, H-20)]
for cx, cy in corners:
pygame.draw.circle(surf, PINK, (cx, cy), 3)
draw_glow(surf, PINK, (cx, cy), 20, int(corner_glow*0.5))
# Animated border
border_col = (
int(60 + 40 * math.sin(t)),
int(20 + 20 * math.sin(t + 1)),
int(120 + 60 * math.sin(t + 2))
)
pygame.draw.rect(surf, border_col, (5, 5, W-10, H-10), 2, border_radius=8)
# Title text
try:
font = pygame.font.SysFont("Arial", 22, bold=True)
font_sm = pygame.font.SysFont("Arial", 13)
except:
font = pygame.font.Font(None, 28)
font_sm = pygame.font.Font(None, 18)
title = font.render("夢の少女", True, CYAN)
sub = font_sm.render("DREAM GIRL ✦ ANIME STYLE", True, PINK2)
# Glow effect for title
draw_glow(surf, CYAN, (W//2, H-38), 60, 40)
surf.blit(title, title.get_rect(center=(W//2, H-38)))
surf.blit(sub, sub.get_rect(center=(W//2, H-18)))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
running = False
t += 0.03
# Background
draw_gradient_bg(screen)
draw_stars(screen, t)
# Ambient glow behind character
draw_glow(screen, BLUE2, (W//2, 400), 200, 30)
draw_glow(screen, PINK3, (W//2, 300), 150, 20)
# Particles
draw_particles(screen, t)
# Character
draw_girl(screen, t)
# Post effects
draw_scanlines(screen)
draw_frame_decoration(screen, t)
pygame.display.flip()
clock.tick(60)
pygame.quit()
sys.exit()

546
jrpg/island_novel.py Normal file
View File

@@ -0,0 +1,546 @@
"""
🏝️ ОСТРОВ ДВУХ СЕРДЕЦ — визуальная новелла
Запуск: 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()

970
jrpg/jrpg_game.py Normal file
View File

@@ -0,0 +1,970 @@
"""
Пиксельная деревня - JRPG
Уютная маленькая JRPG с деревней, лесом и пошаговыми боями!
Требования: pip install pygame
Запуск: python jrpg_game.py
Управление:
Стрелки - Движение
E / Пробел - Взаимодействие / Подтвердить
Escape - Отмена / Назад
"""
import pygame
import sys
import random
import math
pygame.init()
# ── Константы ──────────────────────────────────────────────────────────────
W, H = 640, 480
TILE = 32
FPS = 60
# Яркая пиксельная палитра
C = {
"bg": (20, 20, 35),
"sky": (100, 180, 255),
"grass": (80, 200, 80),
"dark_grass":(60, 160, 60),
"path": (210, 180, 130),
"water": (60, 160, 220),
"tree": (40, 140, 40),
"tree_top": (50, 200, 60),
"trunk": (140, 90, 40),
"house_w": (240, 220, 200),
"house_r": (220, 60, 60),
"store_w": (200, 220, 255),
"store_r": (60, 100, 220),
"door": (140, 80, 30),
"window": (180, 230, 255),
"sign": (240, 200, 100),
"white": (255, 255, 255),
"black": (0, 0, 0),
"gray": (160, 160, 160),
"dark": (30, 30, 50),
"panel": (20, 20, 50),
"panel2": (40, 40, 80),
"border": (120, 100, 200),
"gold": (255, 210, 50),
"red": (240, 60, 60),
"green": (80, 220, 100),
"blue": (80, 160, 240),
"pink": (255, 140, 200),
"purple": (180, 100, 255),
"yellow": (255, 240, 80),
"orange": (255, 160, 40),
"hp_red": (220, 50, 50),
"mp_blue": (50, 100, 220),
"xp_green": (50, 200, 80),
}
screen = pygame.display.set_mode((W, H), pygame.SCALED | pygame.RESIZABLE)
pygame.display.set_caption("Пиксельная деревня")
clock = pygame.time.Clock()
# Шрифты с поддержкой кириллицы
FONT_PATHS = [
"/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf",
"/usr/share/fonts/truetype/freefont/FreeMonoBold.ttf",
"/usr/share/fonts/truetype/liberation/LiberationMono-Bold.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
]
FONT_PATHS_REGULAR = [
"/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
"/usr/share/fonts/truetype/freefont/FreeMono.ttf",
"/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
]
def load_font(paths, size):
for p in paths:
try:
return pygame.font.Font(p, size)
except:
pass
return pygame.font.SysFont("dejavusansmono", size, bold=True)
font_lg = load_font(FONT_PATHS, 20)
font_md = load_font(FONT_PATHS, 15)
font_sm = load_font(FONT_PATHS_REGULAR, 12)
# ── Вспомогательные функции ────────────────────────────────────────────────
def txt(surface, text, x, y, color, fnt=None):
fnt = fnt or font_md
surface.blit(fnt.render(str(text), True, color), (x, y))
def draw_bar(surf, x, y, w, h, val, mx, color, bg=(60,60,60)):
pygame.draw.rect(surf, bg, (x, y, w, h))
fill = int(w * max(0, val) / max(1, mx))
pygame.draw.rect(surf, color, (x, y, fill, h))
pygame.draw.rect(surf, C["white"], (x, y, w, h), 1)
def panel(surf, x, y, w, h, border=True):
pygame.draw.rect(surf, C["panel"], (x, y, w, h))
if border:
pygame.draw.rect(surf, C["border"], (x, y, w, h), 2)
def flash_color(base, t, amp=40):
v = int(amp * abs(math.sin(t * 4)))
return tuple(min(255, c + v) for c in base)
# ── Рисование пиксель-арта ──────────────────────────────────────────────────
def draw_tree(surf, x, y):
pygame.draw.rect(surf, C["trunk"], (x+12, y+28, 8, 12))
pygame.draw.polygon(surf, C["tree"], [(x+16,y),(x+2,y+28),(x+30,y+28)])
pygame.draw.polygon(surf, C["tree_top"], [(x+16,y+4),(x+4,y+24),(x+28,y+24)])
def draw_house(surf, x, y, color_w=None, color_r=None):
cw = color_w or C["house_w"]
cr = color_r or C["house_r"]
pygame.draw.rect(surf, cw, (x, y+16, 48, 32))
pygame.draw.polygon(surf, cr, [(x,y+16),(x+24,y),(x+48,y+16)])
pygame.draw.rect(surf, C["door"], (x+18, y+32, 12, 16))
pygame.draw.rect(surf, C["window"], (x+4, y+22, 10, 10))
pygame.draw.rect(surf, C["window"], (x+34, y+22, 10, 10))
def draw_store(surf, x, y):
draw_house(surf, x, y, C["store_w"], C["store_r"])
pygame.draw.rect(surf, C["sign"], (x+4, y+12, 40, 9))
txt(surf, "ЛАВКА", x+5, y+11, C["dark"], font_sm)
def draw_player(surf, x, y, frame=0, facing=0):
bob = int(math.sin(frame * 0.3) * 2)
bx, by = x, y + bob
pygame.draw.rect(surf, C["blue"], (bx+5, by+10, 12, 12))
pygame.draw.rect(surf, (255,210,170), (bx+4, by+2, 14, 12))
pygame.draw.rect(surf, C["orange"], (bx+4, by+2, 14, 5))
ex = bx+7 if facing != 2 else bx+11
pygame.draw.rect(surf, C["dark"], (ex, by+6, 2, 2))
pygame.draw.rect(surf, C["dark"], (ex+4, by+6, 2, 2))
lx = bx+5 + (2 if frame % 20 < 10 else -1)
pygame.draw.rect(surf, C["dark"], (lx, by+22, 4, 6))
pygame.draw.rect(surf, C["dark"], (lx+6, by+22, 4, 6))
def draw_npc_mitsuha(surf, x, y, frame=0):
bob = int(math.sin(frame * 0.2) * 1.5)
bx, by = x, y + bob
pygame.draw.rect(surf, C["pink"], (bx+5, by+10, 12, 12))
pygame.draw.rect(surf, (255,210,170), (bx+4, by+2, 14, 12))
pygame.draw.rect(surf, C["purple"], (bx+4, by+2, 14, 5))
pygame.draw.rect(surf, C["purple"], (bx+14, by+4, 4, 10))
pygame.draw.rect(surf, C["dark"], (bx+7, by+6, 2, 2))
pygame.draw.rect(surf, C["dark"], (bx+11, by+6, 2, 2))
pygame.draw.rect(surf, C["pink"], (bx+5, by+22, 4, 6))
pygame.draw.rect(surf, C["pink"], (bx+11, by+22, 4, 6))
def draw_slime(surf, x, y, frame=0, color=None):
c = color or C["green"]
bounce = int(abs(math.sin(frame * 0.15)) * 3)
bx, by = x, y - bounce
pygame.draw.ellipse(surf, c, (bx+2, by+10, 20, 16))
pygame.draw.ellipse(surf, tuple(min(255,v+40) for v in c), (bx+4, by+10, 16, 10))
pygame.draw.rect(surf, C["white"], (bx+6, by+13, 4, 4))
pygame.draw.rect(surf, C["white"], (bx+14, by+13, 4, 4))
pygame.draw.rect(surf, C["dark"], (bx+7, by+14, 2, 2))
pygame.draw.rect(surf, C["dark"], (bx+15, by+14, 2, 2))
def draw_fairy(surf, x, y, frame=0):
bob = int(math.sin(frame * 0.2) * 4)
bx, by = x, y + bob
wing_surf = pygame.Surface((24, 14), pygame.SRCALPHA)
pygame.draw.ellipse(wing_surf, (200,220,255,160), (0,0,10,10))
pygame.draw.ellipse(wing_surf, (200,220,255,160), (14,2,10,8))
surf.blit(wing_surf, (bx+4, by+8))
pygame.draw.circle(surf, C["yellow"], (bx+12, by+12), 7)
pygame.draw.circle(surf, C["white"], (bx+12, by+12), 5)
pygame.draw.rect(surf, C["dark"], (bx+10, by+10, 2, 2))
pygame.draw.rect(surf, C["dark"], (bx+13, by+10, 2, 2))
# ── Данные игры ────────────────────────────────────────────────────────────
ITEMS = {
"Зелье": {"desc": "Восст. 30 ОЗ", "price": 50, "effect": ("hp", 30)},
"Хай-зелье": {"desc": "Восст. 80 ОЗ", "price": 120, "effect": ("hp", 80)},
"Эфир": {"desc": "Восст. 20 ОМ", "price": 60, "effect": ("mp", 20)},
"Эликсир": {"desc": "Полное ОЗ", "price": 300, "effect": ("hp", 9999)},
}
MONSTERS = [
{"name": "Зел. слизень", "hp": 20, "mp": 0, "atk": 5, "def": 2, "xp": 10, "gold": 8, "color": C["green"]},
{"name": "Син. слизень", "hp": 28, "mp": 0, "atk": 6, "def": 3, "xp": 14, "gold": 10, "color": C["blue"]},
{"name": "Розовая фея", "hp": 18, "mp": 15, "atk": 8, "def": 1, "xp": 18, "gold": 14, "color": C["pink"]},
{"name": "Лесной огонёк","hp": 35, "mp": 20, "atk": 10, "def": 4, "xp": 25, "gold": 20, "color": C["purple"]},
]
MITSUHA_LINES = [
"Привет! Какой чудесный день, правда?",
"Говорят, в лесу живут\nмиленькие монстрики!",
"Будь осторожен, ладно?\nНо и повеселись тоже!",
"В лавке продают отличные зелья~",
"Как я люблю нашу деревню!",
"О! Ты сегодня выглядишь сильнее!",
]
HOME_LINES = [
"Уютный домик. Ты хорошо отдохнул!",
"Сладкие сны ждут тебя~",
"Дома и стены помогают!",
]
# ── Игрок ──────────────────────────────────────────────────────────────────
class Player:
def __init__(self):
self.name = "Герой"
self.x, self.y = 5, 6
self.hp, self.max_hp = 80, 80
self.mp, self.max_mp = 30, 30
self.atk = 15
self.dfn = 5
self.xp, self.xp_next = 0, 50
self.level = 1
self.gold = 100
self.inventory = {"Зелье": 2}
self.frame = 0
self.facing = 1
self.px = self.x * TILE
self.py = self.y * TILE
def gain_xp(self, amount):
msgs = []
self.xp += amount
while self.xp >= self.xp_next:
self.xp -= self.xp_next
self.level += 1
self.xp_next = int(self.xp_next * 1.4)
self.max_hp += 15
self.hp = self.max_hp
self.max_mp += 5
self.mp = self.max_mp
self.atk += 3
self.dfn += 1
msgs.append(f"Новый уровень! Ур.{self.level}!")
return msgs
def add_item(self, item):
self.inventory[item] = self.inventory.get(item, 0) + 1
def use_item(self, item):
if self.inventory.get(item, 0) <= 0:
return False, "Предметов нет!"
eff = ITEMS[item]["effect"]
self.inventory[item] -= 1
if self.inventory[item] == 0:
del self.inventory[item]
if eff[0] == "hp":
healed = min(eff[1], self.max_hp - self.hp)
self.hp = min(self.max_hp, self.hp + eff[1])
return True, f"Восст. {healed} ОЗ!"
elif eff[0] == "mp":
self.mp = min(self.max_mp, self.mp + eff[1])
return True, f"Восст. {eff[1]} ОМ!"
return False, "Ничего не произошло."
# ── Карты ──────────────────────────────────────────────────────────────────
TOWN_MAP = [
"WWWWWWWWWWWWWWWWWWWW",
"W..................W",
"W..H.......S.....tW",
"W..................W",
"W...........ttt...W",
"W....t............W",
"W..................W",
"W....P.....M......W",
"W..................W",
"W..........t......W",
"W...##.....t......W",
"W...##............W",
"W.................W",
"WWWWWWWWWFWWWWWWWWW",
]
FOREST_MAP = [
"TTTTTTTTTTTTTTTTTTTT",
"T.t....t....t.....T",
"T....t.....t......T",
"T.t........t....t.T",
"T.....t...........T",
"T....t...t........T",
"T.........t.......T",
"T...t.............T",
"T....t....t.......T",
"T.................T",
"T.....t...........T",
"T.....t...........T",
"T.................T",
"TTTTTTTTTTTTTTTTTTTT",
]
# ── Камера ─────────────────────────────────────────────────────────────────
class Camera:
def __init__(self):
self.ox = 0
self.oy = 0
def update(self, px, py, map_w, map_h):
self.ox = px - W // 2 + TILE // 2
self.oy = py - H // 2 + TILE // 2
self.ox = max(0, min(self.ox, map_w * TILE - W))
self.oy = max(0, min(self.oy, map_h * TILE - H))
def world_to_screen(self, wx, wy):
return wx - self.ox, wy - self.oy
# ── Система боя ────────────────────────────────────────────────────────────
class Battle:
def __init__(self, player, monster_data):
self.player = player
self.mon = dict(monster_data)
self.mon_hp = self.mon["hp"]
self.mon_max_hp = self.mon["hp"]
self.state = "player_turn"
self.log = [f"Появился {self.mon['name']}!"]
self.frame = 0
self.selected = 0
self.menu = "main"
self.item_list = []
self.shake_player = 0
self.shake_enemy = 0
self.result_msgs = []
def add_log(self, msg):
self.log.append(msg)
if len(self.log) > 4:
self.log.pop(0)
def player_attack(self):
dmg = max(1, self.player.atk - self.mon["def"] + random.randint(-3, 3))
self.mon_hp -= dmg
self.add_log(f"Ты нанёс {dmg} урона!")
self.shake_enemy = 10
if self.mon_hp <= 0:
self.mon_hp = 0
self.end_battle(True)
else:
self.state = "enemy_turn"
def player_magic(self):
if self.player.mp < 8:
self.add_log("Мало маны!")
return
self.player.mp -= 8
dmg = max(1, self.player.atk * 2 - self.mon["def"] + random.randint(-2, 5))
self.mon_hp -= dmg
self.add_log(f"Магия! {dmg} урона! (-8 ОМ)")
self.shake_enemy = 12
if self.mon_hp <= 0:
self.mon_hp = 0
self.end_battle(True)
else:
self.state = "enemy_turn"
def player_defend(self):
self.add_log("Ты принял защитную стойку!")
self.player.dfn += 5
self._defending = True
self.state = "enemy_turn"
def enemy_turn(self):
if self.state != "enemy_turn":
return
dmg = max(1, self.mon["atk"] - self.player.dfn + random.randint(-2, 3))
self.player.hp -= dmg
self.add_log(f"{self.mon['name']} нанёс {dmg} урона!")
self.shake_player = 10
if hasattr(self, "_defending"):
self.player.dfn -= 5
del self._defending
if self.player.hp <= 0:
self.player.hp = 0
self.end_battle(False)
else:
self.state = "player_turn"
def end_battle(self, won):
if won:
xp = self.mon["xp"]
gold = self.mon["gold"]
self.player.gold += gold
lvl_msgs = self.player.gain_xp(xp)
self.add_log(f"Победа! +{xp} ОП, +{gold} зол.")
for m in lvl_msgs:
self.add_log(m)
self.result_msgs = ["Победа!", f"+{xp} ОП +{gold} золота"] + lvl_msgs
self.state = "win"
else:
self.player.hp = self.player.max_hp // 4
self.add_log("Ты потерпел поражение...")
self.result_msgs = ["Ты упал в обморок!", "Очнулся дома."]
self.state = "lose"
def draw(self, surf):
self.frame += 1
for yy in range(H):
ratio = yy / H
r = int(20 + ratio * 20)
g = int(10 + ratio * 20)
b = int(40 + ratio * 30)
pygame.draw.line(surf, (r, g, b), (0, yy), (W, yy))
random.seed(42)
for _ in range(40):
sx = random.randint(0, W)
sy = random.randint(0, H // 2)
br = 150 + int(50 * math.sin(self.frame * 0.05 + sx))
pygame.draw.rect(surf, (br, br, br), (sx, sy, 2, 2))
random.seed()
pygame.draw.rect(surf, (60, 160, 60), (0, H-80, W, 80))
pygame.draw.rect(surf, (40, 120, 40), (0, H-82, W, 4))
ex = W * 3 // 4 - 24
ey = H // 2 - 40
esx = int(math.sin(self.shake_enemy * 0.8) * 5) if self.shake_enemy > 0 else 0
self.shake_enemy = max(0, self.shake_enemy - 1)
mon_name = self.mon["name"]
if "слизень" in mon_name:
c = self.mon["color"]
for _ in range(3):
bc = int(abs(math.sin(self.frame * 0.1 + _)) * 3)
pygame.draw.ellipse(surf, c, (ex+esx+10, ey+bc+20, 60, 48))
pygame.draw.ellipse(surf, tuple(min(255,v+60) for v in c), (ex+esx+16, ey+24, 46, 30))
pygame.draw.rect(surf, C["white"], (ex+esx+22, ey+32, 10, 10))
pygame.draw.rect(surf, C["white"], (ex+esx+42, ey+32, 10, 10))
pygame.draw.rect(surf, C["dark"], (ex+esx+24, ey+34, 6, 6))
pygame.draw.rect(surf, C["dark"], (ex+esx+44, ey+34, 6, 6))
elif "фея" in mon_name:
bob = int(math.sin(self.frame * 0.2) * 6)
pygame.draw.ellipse(surf, (200, 230, 255), (ex+esx-10, ey+bob+30, 30, 20))
pygame.draw.ellipse(surf, (200, 230, 255), (ex+esx+60, ey+bob+30, 30, 20))
pygame.draw.circle(surf, C["yellow"], (ex+esx+40, ey+bob+40), 22)
pygame.draw.circle(surf, C["white"], (ex+esx+40, ey+bob+40), 16)
pygame.draw.rect(surf, C["dark"], (ex+esx+32, ey+bob+34, 6, 6))
pygame.draw.rect(surf, C["dark"], (ex+esx+44, ey+bob+34, 6, 6))
elif "огонёк" in mon_name:
for ring in range(3):
r2 = 25 + ring * 10 + int(math.sin(self.frame * 0.1 + ring) * 4)
pygame.draw.circle(surf, C["purple"], (ex+esx+40, ey+40), r2, 2)
pygame.draw.circle(surf, C["purple"], (ex+esx+40, ey+40), 18)
pygame.draw.circle(surf, C["white"], (ex+esx+40, ey+40), 10)
draw_bar(surf, ex+esx+5, ey-12, 70, 8, self.mon_hp, self.mon_max_hp, C["hp_red"])
txt(surf, mon_name, ex+esx-20, ey-28, C["white"], font_sm)
psx = W // 4 - 12
psy = H // 2
pshake = int(math.sin(self.shake_player * 0.8) * 5) if self.shake_player > 0 else 0
self.shake_player = max(0, self.shake_player - 1)
draw_player(surf, psx + pshake, psy, self.frame, 3)
# Панель характеристик игрока
panel(surf, 10, H-130, 195, 120)
txt(surf, f"Ур.{self.player.level} {self.player.name}", 18, H-126, C["gold"])
draw_bar(surf, 18, H-108, 172, 10, self.player.hp, self.player.max_hp, C["hp_red"])
txt(surf, f"ОЗ {self.player.hp}/{self.player.max_hp}", 18, H-96, C["white"], font_sm)
draw_bar(surf, 18, H-82, 172, 10, self.player.mp, self.player.max_mp, C["mp_blue"])
txt(surf, f"ОМ {self.player.mp}/{self.player.max_mp}", 18, H-70, C["white"], font_sm)
draw_bar(surf, 18, H-56, 172, 8, self.player.xp, self.player.xp_next, C["xp_green"])
txt(surf, f"ОП {self.player.xp}/{self.player.xp_next}", 18, H-44, C["white"], font_sm)
# Журнал боя
panel(surf, W//2-160, H-130, 320, 70)
for i, line in enumerate(self.log[-3:]):
txt(surf, line, W//2-152, H-126 + i*22, C["white"], font_sm)
# Меню / результат
if self.state in ("win", "lose"):
panel(surf, W//2-130, H//2-60, 260, 130)
color = C["gold"] if self.state == "win" else C["red"]
txt(surf, self.result_msgs[0], W//2-90, H//2-50, color)
for i, m in enumerate(self.result_msgs[1:]):
txt(surf, m, W//2-110, H//2-20 + i*22, C["white"], font_sm)
txt(surf, "Нажми E чтобы продолжить", W//2-110, H//2+62, C["gray"], font_sm)
elif self.state == "player_turn":
if self.menu == "main":
panel(surf, W-215, H-130, 205, 120)
options = ["Атаковать", "Магия (8ОМ)", "Защититься", "Предметы"]
for i, opt in enumerate(options):
color = C["yellow"] if i == self.selected else C["white"]
prefix = "> " if i == self.selected else " "
txt(surf, prefix + opt, W-210, H-122 + i*26, color)
elif self.menu == "items":
panel(surf, W-230, H-150, 220, 140)
txt(surf, "Предметы:", W-222, H-146, C["gold"])
if not self.item_list:
txt(surf, "Нет предметов!", W-210, H-120, C["gray"])
else:
for i, (name, qty) in enumerate(self.item_list):
color = C["yellow"] if i == self.selected else C["white"]
prefix = "> " if i == self.selected else " "
txt(surf, f"{prefix}{name} x{qty}", W-222, H-120 + i*24, color, font_sm)
txt(surf, "ESC=Назад", W-222, H-18, C["gray"], font_sm)
def handle_input(self, event):
if event.type != pygame.KEYDOWN:
return None
if self.state in ("win", "lose"):
if event.key in (pygame.K_e, pygame.K_SPACE, pygame.K_RETURN):
return self.state
return None
if self.state != "player_turn":
return None
if self.menu == "main":
if event.key == pygame.K_UP:
self.selected = (self.selected - 1) % 4
elif event.key == pygame.K_DOWN:
self.selected = (self.selected + 1) % 4
elif event.key in (pygame.K_e, pygame.K_SPACE, pygame.K_RETURN):
if self.selected == 0:
self.player_attack()
elif self.selected == 1:
self.player_magic()
elif self.selected == 2:
self.player_defend()
pygame.time.wait(400)
self.enemy_turn()
elif self.selected == 3:
self.item_list = [(n, q) for n, q in self.player.inventory.items() if n in ITEMS]
self.selected = 0
self.menu = "items"
elif self.menu == "items":
if event.key == pygame.K_ESCAPE:
self.menu = "main"
self.selected = 0
elif event.key == pygame.K_UP:
self.selected = max(0, self.selected - 1)
elif event.key == pygame.K_DOWN:
self.selected = min(len(self.item_list)-1, self.selected + 1)
elif event.key in (pygame.K_e, pygame.K_SPACE, pygame.K_RETURN):
if self.item_list:
name, _ = self.item_list[self.selected]
ok, msg = self.player.use_item(name)
self.add_log(msg)
self.item_list = [(n, q) for n, q in self.player.inventory.items() if n in ITEMS]
if ok:
self.menu = "main"
self.selected = 0
self.state = "enemy_turn"
if self.state == "enemy_turn":
pygame.time.wait(300)
self.enemy_turn()
return None
# ── Лавка ──────────────────────────────────────────────────────────────────
class Shop:
def __init__(self, player):
self.player = player
self.items = list(ITEMS.keys())
self.selected = 0
def draw(self, surf):
panel(surf, W//2-190, 55, 380, 370)
txt(surf, "= ЛАВКА ПРЕДМЕТОВ =", W//2-130, 65, C["gold"])
txt(surf, f"Золото: {self.player.gold}", W//2+80, 65, C["yellow"], font_sm)
pygame.draw.line(surf, C["border"], (W//2-185, 90), (W//2+185, 90), 1)
for i, name in enumerate(self.items):
info = ITEMS[name]
y = 100 + i * 58
color = C["yellow"] if i == self.selected else C["white"]
prefix = "> " if i == self.selected else " "
txt(surf, prefix + name, W//2-180, y, color)
txt(surf, info["desc"], W//2-165, y+22, C["gray"], font_sm)
txt(surf, f"{info['price']} зол.", W//2+90, y, C["gold"], font_sm)
txt(surf, "E=Купить ESC=Выйти", W//2-100, 382, C["gray"], font_sm)
def handle_input(self, event):
if event.type != pygame.KEYDOWN:
return False
if event.key == pygame.K_ESCAPE:
return True
if event.key == pygame.K_UP:
self.selected = (self.selected - 1) % len(self.items)
elif event.key == pygame.K_DOWN:
self.selected = (self.selected + 1) % len(self.items)
elif event.key in (pygame.K_e, pygame.K_SPACE, pygame.K_RETURN):
name = self.items[self.selected]
price = ITEMS[name]["price"]
if self.player.gold >= price:
self.player.gold -= price
self.player.add_item(name)
return False
# ── Диалог ─────────────────────────────────────────────────────────────────
class Dialogue:
def __init__(self, lines):
self.lines = lines if isinstance(lines, list) else [lines]
self.idx = 0
@property
def done(self):
return self.idx >= len(self.lines)
def advance(self):
self.idx += 1
def draw(self, surf):
if self.done:
return
panel(surf, 30, H-110, W-60, 100)
line = self.lines[self.idx]
for i, l in enumerate(line.split("\n")):
txt(surf, l, 46, H-100 + i*22, C["white"])
txt(surf, "V E", W-70, H-24, C["gray"], font_sm)
# ── Отрисовка карты ────────────────────────────────────────────────────────
def tile_walkable(tile):
return tile not in ("W", "t", "#", "T")
def render_map(surf, map_data, cam, frame):
rows = len(map_data)
for row in range(rows):
cols = len(map_data[row])
for col in range(cols):
tile = map_data[row][col]
wx = col * TILE
wy = row * TILE
sx, sy = cam.world_to_screen(wx, wy)
if sx > W+TILE or sy > H+TILE or sx < -TILE or sy < -TILE:
continue
grass_c = C["dark_grass"] if (row + col) % 2 == 0 else C["grass"]
pygame.draw.rect(surf, grass_c, (sx, sy, TILE, TILE))
if tile == "W":
pygame.draw.rect(surf, (100, 80, 60), (sx, sy, TILE, TILE))
pygame.draw.rect(surf, (80, 60, 40), (sx, sy, TILE, TILE), 2)
elif tile == "#":
wo = int(math.sin(frame * 0.05 + col * 0.5) * 2)
pygame.draw.rect(surf, C["water"], (sx, sy+wo, TILE, TILE))
pygame.draw.rect(surf, (100, 200, 255), (sx+4, sy+4+wo, TILE-8, 4), 1)
elif tile == "t":
draw_tree(surf, sx, sy - 8)
elif tile == "H":
draw_house(surf, sx-8, sy-16)
elif tile == "S":
draw_store(surf, sx-8, sy-16)
elif tile == "F":
pygame.draw.rect(surf, C["dark_grass"], (sx, sy, TILE, TILE))
pygame.draw.rect(surf, C["green"], (sx+4, sy+4, TILE-8, TILE-8), 2)
elif tile == "T":
pygame.draw.rect(surf, C["path"], (sx, sy, TILE, TILE))
# ── Основная игра ──────────────────────────────────────────────────────────
class Game:
TOWN = "town"
FOREST = "forest"
def __init__(self):
self.player = Player()
self.cam = Camera()
self.scene = self.TOWN
self.current_map = TOWN_MAP
self.dialogue = None
self.shop = None
self.battle = None
self.frame = 0
self.npc_frame = 0
self.message = None
self.mitsuha_dialogue_idx = 0
self.sparks = []
def show_message(self, text, duration=120):
self.message = (text, duration)
def add_spark(self, x, y, color=None):
c = color or random.choice([C["yellow"], C["white"], C["gold"], C["pink"]])
self.sparks.append({
"x": x, "y": y,
"vx": random.uniform(-1.5, 1.5),
"vy": random.uniform(-3, -0.5),
"life": 40, "color": c
})
def get_tile(self, tx, ty):
m = self.current_map
if ty < 0 or ty >= len(m) or tx < 0 or tx >= len(m[ty]):
return "W"
return m[ty][tx]
def try_move(self, dx, dy):
nx = self.player.x + dx
ny = self.player.y + dy
if not tile_walkable(self.get_tile(nx, ny)):
return
if self.scene == self.TOWN and self.get_tile(nx, ny) == "F":
self.scene = self.FOREST
self.current_map = FOREST_MAP
self.player.x, self.player.y = 10, 12
self.player.px = self.player.x * TILE
self.player.py = self.player.y * TILE
self.show_message("Ты вошёл в лес!")
return
if self.scene == self.FOREST and self.get_tile(nx, ny) == "T":
self.scene = self.TOWN
self.current_map = TOWN_MAP
self.player.x, self.player.y = 9, 12
self.player.px = self.player.x * TILE
self.player.py = self.player.y * TILE
self.show_message("Ты вернулся в деревню!")
return
self.player.x = nx
self.player.y = ny
self.player.px = nx * TILE
self.player.py = ny * TILE
if dx > 0: self.player.facing = 3
elif dx < 0: self.player.facing = 2
elif dy > 0: self.player.facing = 1
else: self.player.facing = 0
if self.scene == self.FOREST:
if random.random() < 0.2:
mon = random.choice(MONSTERS)
self.battle = Battle(self.player, mon)
def try_interact(self):
fx, fy = self.player.x, self.player.y
facing = self.player.facing
if facing == 0: fy -= 1
elif facing == 1: fy += 1
elif facing == 2: fx -= 1
elif facing == 3: fx += 1
tile = self.get_tile(fx, fy)
if self.scene == self.TOWN:
for row_i, row in enumerate(TOWN_MAP):
if "M" in row:
mx, my = row.index("M"), row_i
if abs(mx - self.player.x) + abs(my - self.player.y) <= 1:
line = MITSUHA_LINES[self.mitsuha_dialogue_idx % len(MITSUHA_LINES)]
self.mitsuha_dialogue_idx += 1
self.dialogue = Dialogue(line)
return
if tile == "H":
self.player.hp = self.player.max_hp
self.player.mp = self.player.max_mp
self.dialogue = Dialogue(random.choice(HOME_LINES) + "\n(ОЗ и ОМ восстановлены!)")
for _ in range(12):
self.add_spark(self.player.px + random.randint(-20, 20),
self.player.py + random.randint(-30, 10), C["gold"])
elif tile == "S":
self.shop = Shop(self.player)
def update(self):
self.frame += 1
self.npc_frame += 1
self.player.frame += 1
for s in self.sparks[:]:
s["x"] += s["vx"]
s["y"] += s["vy"]
s["vy"] += 0.1
s["life"] -= 1
if s["life"] <= 0:
self.sparks.remove(s)
if self.message:
text, timer = self.message
self.message = (text, timer - 1) if timer > 0 else None
def draw_world(self, surf):
surf.fill(C["sky"])
render_map(surf, self.current_map, self.cam, self.frame)
if self.scene == self.TOWN:
for row_i, row in enumerate(TOWN_MAP):
if "M" in row:
mx, my = row.index("M"), row_i
sx, sy = self.cam.world_to_screen(mx * TILE, my * TILE)
draw_npc_mitsuha(surf, sx, sy, self.npc_frame)
panel(surf, sx-4, sy-22, 64, 16)
txt(surf, "Мицуха", sx-2, sy-21, C["pink"], font_sm)
px, py = self.cam.world_to_screen(self.player.px, self.player.py)
draw_player(surf, px, py, self.player.frame, self.player.facing)
for s in self.sparks:
c = tuple(min(255, v) for v in s["color"])
pygame.draw.rect(surf, c, (int(s["x"]) - self.cam.ox, int(s["y"]) - self.cam.oy, 3, 3))
def draw_hud(self, surf):
panel(surf, 8, 8, 210, 68)
txt(surf, f"Ур.{self.player.level} {self.player.name}", 14, 12, C["gold"])
draw_bar(surf, 14, 32, 190, 8, self.player.hp, self.player.max_hp, C["hp_red"])
txt(surf, f"ОЗ {self.player.hp}/{self.player.max_hp}", 14, 42, C["white"], font_sm)
draw_bar(surf, 14, 54, 190, 8, self.player.mp, self.player.max_mp, C["mp_blue"])
txt(surf, f"ОМ {self.player.mp}/{self.player.max_mp}", 14, 64, C["white"], font_sm)
panel(surf, W-165, 8, 157, 26)
txt(surf, f"Золото: {self.player.gold}", W-159, 12, C["gold"], font_sm)
loc = "** Деревня **" if self.scene == self.TOWN else "~~ Лес ~~"
lw = font_sm.size(loc)[0] + 16
panel(surf, W//2 - lw//2, 8, lw, 26)
txt(surf, loc, W//2 - lw//2 + 8, 12, C["white"], font_sm)
txt(surf, "Стрелки: движение E: действие", 10, H-18, C["gray"], font_sm)
if self.message:
text, timer = self.message
tw = font_md.size(text)[0] + 24
panel(surf, W//2 - tw//2, H//2-20, tw, 40)
fc = flash_color(C["white"], self.frame * 0.02)
txt(surf, text, W//2 - tw//2 + 12, H//2-12, fc)
def run(self):
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
if self.dialogue:
self.dialogue = None
elif self.shop:
self.shop = None
elif self.battle:
pass
else:
running = False
if self.battle:
result = self.battle.handle_input(event)
if result in ("win", "lose"):
if result == "lose":
self.scene = self.TOWN
self.current_map = TOWN_MAP
self.player.x, self.player.y = 5, 6
self.player.px = self.player.x * TILE
self.player.py = self.player.y * TILE
self.battle = None
continue
if self.shop:
if self.shop.handle_input(event):
self.shop = None
continue
if self.dialogue:
if event.type == pygame.KEYDOWN and event.key in (pygame.K_e, pygame.K_SPACE, pygame.K_RETURN):
self.dialogue.advance()
if self.dialogue.done:
self.dialogue = None
continue
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
self.try_move(0, -1)
elif event.key == pygame.K_DOWN:
self.try_move(0, 1)
elif event.key == pygame.K_LEFT:
self.try_move(-1, 0)
elif event.key == pygame.K_RIGHT:
self.try_move(1, 0)
elif event.key in (pygame.K_e, pygame.K_SPACE, pygame.K_RETURN):
self.try_interact()
self.update()
self.cam.update(self.player.px, self.player.py,
len(self.current_map[0]), len(self.current_map))
if self.battle:
self.battle.draw(screen)
else:
self.draw_world(screen)
self.draw_hud(screen)
if self.shop:
self.shop.draw(screen)
if self.dialogue:
self.dialogue.draw(screen)
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
sys.exit()
# ── Заставка ───────────────────────────────────────────────────────────────
def title_screen():
frame = 0
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
if event.type == pygame.KEYDOWN:
return
frame += 1
screen.fill(C["bg"])
random.seed(7)
for _ in range(60):
sx = random.randint(0, W)
sy = random.randint(0, H)
br = 100 + int(80 * math.sin(frame * 0.03 + sx * 0.1))
pygame.draw.rect(screen, (br, br, int(br*0.8)), (sx, sy, 2, 2))
random.seed()
tc = flash_color(C["gold"], frame * 0.04, 30)
title = font_lg.render(">> Пиксельная деревня <<", True, tc)
screen.blit(title, (W//2 - title.get_width()//2, 90))
sub = font_md.render("~ Уютная маленькая JRPG ~", True, C["pink"])
screen.blit(sub, (W//2 - sub.get_width()//2, 128))
draw_player(screen, W//2-80, 220, frame)
draw_npc_mitsuha(screen, W//2-20, 220, frame)
draw_slime(screen, W//2+60, 230, frame, C["green"])
draw_fairy(screen, W//2+100, 220, frame)
features = [
"[Д] Деревня: дом, лавка и пруд",
"[?] Поговори с соседкой Мицухой",
"[Л] Лес с милыми монстриками",
"[!] Пошаговые бои",
"[*] Повышай уровень героя!",
]
for i, f in enumerate(features):
fc = C["white"] if i % 2 == 0 else C["gray"]
fs = font_sm.render(f, True, fc)
screen.blit(fs, (W//2 - fs.get_width()//2, 300 + i*22))
if int(frame / 30) % 2 == 0:
ps = font_md.render("Нажми любую клавишу!", True, C["yellow"])
screen.blit(ps, (W//2 - ps.get_width()//2, 420))
pygame.display.flip()
clock.tick(FPS)
if __name__ == "__main__":
title_screen()
game = Game()
game.run()

1017
jrpg/jrpg_game_en.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ body {
font-family: "Roboto", sans-serif;
font-size: 1em;
font-size: 1.5em;
letter-spacing: 0.1px;
color: lighter;
text-rendering: optimizeLegibility;