Загрузка данных
import random
import sqlite3
import pgzrun
import os
import math
# ─── Диагностика: проверка файлов ───
def verify_assets():
required_images = ['meni.png', 'mon.png', 'map.png']
missing = []
for img in required_images:
path = os.path.join('images', img)
if not os.path.exists(path):
missing.append(img)
if missing:
print(f"Не найдены файлы: {missing}")
print("Убедитесь, что папка 'images' с файлами лежит рядом с game.py")
return False
return True
if not verify_assets():
exit()
# ─── Настройки окна ───
WIDTH = 1280
HEIGHT = 720
# ─── База данных ───
def init_db():
conn = sqlite3.connect('game.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS player_stats
(id INTEGER PRIMARY KEY, hp INTEGER, damage INTEGER)''')
c.execute('SELECT COUNT(*) FROM player_stats')
if c.fetchone()[0] == 0:
c.execute('INSERT INTO player_stats (hp, damage) VALUES (300, 15)')
conn.commit()
conn.close()
def load_stats():
conn = sqlite3.connect('game.db')
c = conn.cursor()
c.execute('SELECT hp, damage FROM player_stats WHERE id = 1')
hp, damage = c.fetchone()
conn.close()
return hp, damage
def save_stats(hp, damage):
conn = sqlite3.connect('game.db')
c = conn.cursor()
c.execute('UPDATE player_stats SET hp = ?, damage = ? WHERE id = 1', (hp, damage))
conn.commit()
conn.close()
# ─── Инициализация ───
init_db()
player_hp, player_damage = load_stats()
try:
player = Actor('meni', (100, 100))
background = Actor('map', (WIDTH // 2, HEIGHT // 2))
except Exception as e:
print(f"Критическая ошибка загрузки изображений: {e}")
exit()
monster = None # глобальная переменная: пока нет монстра
# ─── Константы ───
PLAYER_SPEED = 5
MONSTER_SPEED = 1.5
ATTACK_RANGE = 120
MONSTER_ATTACK_RANGE = 60
MONSTER_ATTACK_COOLDOWN = 90
MONSTER_DAMAGE_MIN = 8
MONSTER_DAMAGE_MAX = 18
BASE_MONSTER_HP = 500
BASE_PLAYER_HP = 300
BASE_PLAYER_DAMAGE = 15
SHIELD_DURATION_MAX = 300
SHIELD_REDUCTION = 0.5
COLLISION_DAMAGE = player_damage // 2
COLLISION_KNOCKBACK = 0
COLLISION_COOLDOWN_FRAMES = 30
SPAWN_DELAY_FRAMES = 30 # небольшая пауза после смерти монстра
# ─── Переменные игры ───
game_state = 'start'
monster_hp = 0
score = 0
monster_attack_timer = 0
monster_initial_delay = 60
has_shield = False
has_gun = True
shield_timer = 0
last_collision_frame = -100
frame_counter = 0
spawn_delay_counter = 0 # счётчик задержки перед спавном нового монстра
# ─── Вспомогательные функции ───
def spawn_monster():
global monster, monster_hp
# ищем позицию подальше от игрока
x = random.randint(100, WIDTH - 100)
y = random.randint(100, HEIGHT - 100)
while math.hypot(x - player.x, y - player.y) < 400: # минимум 400 px от игрока
x = random.randint(100, WIDTH - 100)
y = random.randint(100, HEIGHT - 100)
monster = Actor('mon', (x, y))
monster_hp = BASE_MONSTER_HP + score * 150
print(f"[DEBUG] Спавн монстра: HP={monster_hp}, позиция=({x}, {y})")
def move_monster_toward_player():
if monster is None:
return
dx = player.x - monster.x
dy = player.y - monster.y
dist = math.hypot(dx, dy)
if dist > 0:
dx /= dist
dy /= dist
monster.x += dx * MONSTER_SPEED
monster.y += dy * MONSTER_SPEED
# ─── Отрисовка ───
def draw():
screen.clear()
background.draw()
if game_state == 'start':
screen.draw.text(
"НАЖМИТЕ ПРОБЕЛ ДЛЯ НАЧАЛА ИГРЫ",
center=(WIDTH // 2, HEIGHT // 2 - 50),
color='white', fontsize=36
)
screen.draw.text(
"F11 — ПОЛНОЭКРАННЫЙ РЕЖИМ",
center=(WIDTH // 2, HEIGHT // 2 + 50),
color='yellow', fontsize=24
)
elif game_state == 'playing':
player.draw()
if monster is not None and monster_hp > 0:
monster.draw()
screen.draw.text(
f"HP: {monster_hp}",
(monster.x - 20, monster.y - 50),
color='red', fontsize=20
)
# HUD игрока
screen.draw.text(f"HP: {player_hp}", (10, 10), color='red', fontsize=24)
screen.draw.text(f"УРОН: {player_damage}", (10, 40), color='yellow', fontsize=24)
screen.draw.text(f"МОНСТРОВ УБИТО: {score}", (10, 70), color='green', fontsize=24)
y_offset = 100
if has_shield:
screen.draw.text(
f"ЩИТ АКТИВЕН ({shield_timer // 60 + 1}с)",
(10, y_offset), color='blue', fontsize=20
)
y_offset += 30
if not has_gun:
screen.draw.text("ОРУЖИЕ УБРАНО", (10, y_offset), color='orange', fontsize=20)
elif game_state == 'game_over':
screen.draw.text(
"ИГРА ОКОНЧЕНА! НАЖМИТЕ R ДЛЯ ПЕРЕЗАПУСКА",
center=(WIDTH // 2, HEIGHT // 2),
color='white', fontsize=30
)
screen.draw.text(
f"Финальный счёт: {score}",
center=(WIDTH // 2, HEIGHT // 2 + 50),
color='yellow', fontsize=28
)
# ─── Обновление ───
def update():
global player_hp, monster_hp, score, game_state
global monster_attack_timer, monster_initial_delay
global has_shield, has_gun, shield_timer
global player_damage, last_collision_frame, frame_counter
global spawn_delay_counter, monster
frame_counter += 1
if game_state != 'playing':
return
# Щит
if has_shield and shield_timer > 0:
shield_timer -= 1
if shield_timer <= 0:
has_shield = False
# Движение игрока
if keyboard.left and player.x > 30:
player.x -= PLAYER_SPEED
if keyboard.right and player.x < WIDTH - 30:
player.x += PLAYER_SPEED
if keyboard.up and player.y > 30:
player.y -= PLAYER_SPEED
if keyboard.down and player.y < HEIGHT - 30:
player.y += PLAYER_SPEED
# Столкновение и урон от касания
if monster is not None and monster_hp > 0:
dist_to_monster = math.hypot(player.x - monster.x, player.y - monster.y)
if dist_to_monster <= COLLISION_DAMAGE:
if frame_counter - last_collision_frame >= COLLISION_COOLDOWN_FRAMES:
last_collision_frame = frame_counter
monster_hp -= COLLISION_DAMAGE
# Движение монстра к игроку
if monster is not None and monster_hp > 0:
move_monster_toward_player()
# Атака монстра
dist_to_player = math.hypot(monster.x - player.x, monster.y - player.y)
if monster_initial_delay > 0:
monster_initial_delay -= 1
elif dist_to_player <= MONSTER_ATTACK_RANGE:
if monster_attack_timer <= 0:
damage = random.randint(MONSTER_DAMAGE_MIN, MONSTER_DAMAGE_MAX)
if has_shield:
damage = max(1, int(damage * SHIELD_REDUCTION))
player_hp -= damage
monster_attack_timer = MONSTER_ATTACK_COOLDOWN
else:
monster_attack_timer -= 1
else:
if monster_attack_timer > 0:
monster_attack_timer -= 1
# Логика спавна нового монстра после смерти
if spawn_delay_counter > 0:
spawn_delay_counter -= 1
# Во время задержки можно, например, мигать надписью «НОВЫЙ МОНСТР…»
if spawn_delay_counter == 0 and game_state == 'playing':
spawn_monster()
# Смерть игрока
if player_hp <= 0:
player_hp = 0
game_state = 'game_over'
save_stats(player_hp, player_damage)
# ─── Клик мыши (атака игрока) ───
def on_mouse_down(pos, button):
global monster_hp, score, player_damage, monster_attack_timer, monster_initial_delay, monster, spawn_delay_counter
if game_state != 'playing':
return
if not has_gun or monster is None or monster_hp <= 0:
return
if button != mouse.LEFT:
return
dist = math.hypot(player.x - monster.x, player.y - monster.y)
if dist > ATTACK_RANGE:
return
monster_hp -= player_damage
if monster_hp <= 0:
monster_hp = 0
score += 1
if score % 3 == 0:
player_damage += 1
# Ставим задержку перед спавном, чтобы игрок увидел смерть монстра
spawn_delay_counter = SPAWN_DELAY_FRAMES
monster_attack_timer = 0
monster_initial_delay = 60
print("[DEBUG] Монстр убит. Запущена задержка спавна.")
# ─── Клавиши ───
def on_key_down(key):
global game_state, player_hp, player_damage, score, monster_hp
global monster_attack_timer, monster_initial_delay
global has_shield, has_gun, shield_timer, monster, spawn_delay_counter
if key == keys.SPACE and game_state == 'start':
game_state = 'playing'
monster_initial_delay = 60
monster_attack_timer = 0
spawn_monster() # первый монстр при старте
elif key == keys.R and game_state == 'game_over':
game_state = 'start'
player_hp = BASE_PLAYER_HP
player_damage = BASE_PLAYER_DAMAGE
score = 0
monster_hp = 0
monster_attack_timer = 0
monster_initial_delay = 60
has_shield = False
has_gun = True
shield_timer = 0
spawn_delay_counter = 0
save_stats(player_hp, player_damage)
player.pos = (100, 100)
monster = None
elif key == keys.M and game_state == 'playing':
has_shield = True
shield_timer = SHIELD_DURATION_MAX
elif key == keys.L and game_state == 'playing':
has_gun = not has_gun
pgzrun.go()