feat: Initial commit Plants vs Zombies Web Edition v2.4

- 5 Biomes: Day, Night, Pool, Fog, Roof (25 Campaign levels)
- Minigames: Wall-nut Bowling, I Zombie, Roguelike, Boss Rush & Dr. Zomboss
- Systems: Zen Garden, Workshop Crafting, Almanac, Tree of Wisdom, Achievements
- Backend: REST API + SQLite Accounts & Save Synchronization
- Infrastructure: Nginx HTTPS & Docker deployment
This commit is contained in:
Antigravity committed 2026-09-17 09:30:52 +00:00
commit 6457a8904e
50 files changed
+14486

No files matched your search

+327
View File
@@ -0,0 +1,327 @@
#!/usr/bin/env python3
"""
SQLite Database Manager for Plants vs. Zombies Game (ai.qpid.pro)
Zero external dependencies. Manages users, game progress profiles, leaderboards, and bug reports.
"""
import sqlite3
import json
import os
from datetime import datetime
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA_DIR = os.path.join(BASE_DIR, "data")
DB_PATH = os.path.join(DATA_DIR, "pvz.db")
os.makedirs(DATA_DIR, exist_ok=True)
def get_connection():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def init_db():
conn = get_connection()
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL COLLATE NOCASE,
password_hash TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS profiles (
user_id INTEGER PRIMARY KEY,
coins INTEGER DEFAULT 150,
level INTEGER DEFAULT 1,
max_unlocked_level INTEGER DEFAULT 1,
high_score INTEGER DEFAULT 0,
theme TEXT DEFAULT 'classic',
upgrades TEXT DEFAULT '{}',
materials TEXT DEFAULT '{}',
plant_levels TEXT DEFAULT '{}',
perks TEXT DEFAULT '{}',
zen_garden TEXT DEFAULT '{}',
tree_of_wisdom TEXT DEFAULT '{}',
achievements TEXT DEFAULT '{}',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS leaderboards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
player_name TEXT NOT NULL,
mode TEXT NOT NULL,
score INTEGER NOT NULL,
detail TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS bugs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
author TEXT NOT NULL,
category TEXT NOT NULL,
text TEXT NOT NULL,
status TEXT DEFAULT 'новый',
timestamp TEXT NOT NULL,
admin_reply TEXT
)
""")
conn.commit()
conn.close()
def get_default_profile_dict():
return {
"coins": 150,
"level": 1,
"maxUnlockedLevel": 1,
"highScore": 0,
"theme": "classic",
"upgrades": {"extraSlot": False, "hasRake": False},
"materials": {"bioHumus": 10, "scrapMetal": 5, "sunEssence": 20, "crystals": 0},
"plantLevels": {"peashooter": 1, "sunflower": 1, "wallnut": 1, "potatomine": 1, "repeater": 1, "snowpea": 1, "chomper": 1},
"perks": {"magnetism": 0, "startSun": 0, "fastCool": 0, "hardyRoots": 0, "luckyDrop": 0},
"zenGarden": {
"pots": [
{"id": 1, "plantType": "sunflower", "stage": 2, "watered": True, "fertilized": True, "needWaterTime": 0, "matureTime": 0, "dropsCollected": 0},
{"id": 2, "plantType": "peashooter", "stage": 1, "watered": True, "fertilized": False, "needWaterTime": 0, "matureTime": 0, "dropsCollected": 0},
{"id": 3, "plantType": None, "stage": 0, "watered": False, "fertilized": False, "needWaterTime": 0, "matureTime": 0, "dropsCollected": 0},
{"id": 4, "plantType": None, "stage": 0, "watered": False, "fertilized": False, "needWaterTime": 0, "matureTime": 0, "dropsCollected": 0},
{"id": 5, "plantType": None, "stage": 0, "watered": False, "fertilized": False, "needWaterTime": 0, "matureTime": 0, "dropsCollected": 0},
{"id": 6, "plantType": None, "stage": 0, "watered": False, "fertilized": False, "needWaterTime": 0, "matureTime": 0, "dropsCollected": 0},
{"id": 7, "plantType": None, "stage": 0, "watered": False, "fertilized": False, "needWaterTime": 0, "matureTime": 0, "dropsCollected": 0},
{"id": 8, "plantType": None, "stage": 0, "watered": False, "fertilized": False, "needWaterTime": 0, "matureTime": 0, "dropsCollected": 0}
],
"fertilizerCount": 5,
"chocolateCount": 2,
"snail": {"name": "Стинки", "active": False, "speed": 60, "boostTimer": 0, "x": 50, "direction": 1},
"lastVisit": int(datetime.now().timestamp() * 1000)
},
"treeOfWisdom": {"height": 0, "cheats": {}},
"achievements": {}
}
def get_user_by_username(username):
conn = get_connection()
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE username = ? COLLATE NOCASE", (username.strip(),))
user = cursor.fetchone()
conn.close()
return user
def register_user(username, password_hash, initial_data=None):
username = username.strip()
conn = get_connection()
cursor = conn.cursor()
try:
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
cursor.execute(
"INSERT INTO users (username, password_hash, created_at, last_login) VALUES (?, ?, ?, ?)",
(username, password_hash, now_str, now_str)
)
user_id = cursor.lastrowid
profile_data = get_default_profile_dict()
if initial_data and isinstance(initial_data, dict):
for k, v in initial_data.items():
if v is not None:
profile_data[k] = v
cursor.execute("""
INSERT INTO profiles (
user_id, coins, level, max_unlocked_level, high_score, theme,
upgrades, materials, plant_levels, perks, zen_garden, tree_of_wisdom, achievements, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
user_id,
profile_data.get("coins", 150),
profile_data.get("level", 1),
profile_data.get("maxUnlockedLevel", 1),
profile_data.get("highScore", 0),
profile_data.get("theme", "classic"),
json.dumps(profile_data.get("upgrades", {})),
json.dumps(profile_data.get("materials", {})),
json.dumps(profile_data.get("plantLevels", {})),
json.dumps(profile_data.get("perks", {})),
json.dumps(profile_data.get("zenGarden", {})),
json.dumps(profile_data.get("treeOfWisdom", {})),
json.dumps(profile_data.get("achievements", {})),
now_str
))
conn.commit()
conn.close()
return {"success": True, "user_id": user_id, "username": username, "data": profile_data}
except sqlite3.IntegrityError:
conn.close()
return {"success": False, "message": "Пользователь с таким именем уже существует"}
except Exception as e:
conn.close()
return {"success": False, "message": str(e)}
def authenticate_user(username, password_hash):
username = username.strip()
conn = get_connection()
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE username = ? COLLATE NOCASE", (username,))
user = cursor.fetchone()
if not user:
conn.close()
return None
if user["password_hash"] != password_hash:
conn.close()
return {"success": False, "message": "Неверный пароль"}
# Update last login
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
cursor.execute("UPDATE users SET last_login = ? WHERE id = ?", (now_str, user["id"]))
conn.commit()
# Fetch profile
cursor.execute("SELECT * FROM profiles WHERE user_id = ?", (user["id"],))
p = cursor.fetchone()
conn.close()
if not p:
profile_dict = get_default_profile_dict()
else:
profile_dict = {
"coins": p["coins"],
"level": p["level"],
"maxUnlockedLevel": p["max_unlocked_level"],
"highScore": p["high_score"],
"theme": p["theme"],
"upgrades": json.loads(p["upgrades"] or "{}"),
"materials": json.loads(p["materials"] or "{}"),
"plantLevels": json.loads(p["plant_levels"] or "{}"),
"perks": json.loads(p["perks"] or "{}"),
"zenGarden": json.loads(p["zen_garden"] or "{}"),
"treeOfWisdom": json.loads(p["tree_of_wisdom"] or "{}"),
"achievements": json.loads(p["achievements"] or "{}")
}
return {"success": True, "user_id": user["id"], "username": user["username"], "data": profile_dict}
def save_user_profile(username, data):
username = username.strip()
conn = get_connection()
cursor = conn.cursor()
cursor.execute("SELECT id FROM users WHERE username = ? COLLATE NOCASE", (username,))
user = cursor.fetchone()
if not user:
conn.close()
return {"success": False, "message": "Пользователь не найден"}
user_id = user["id"]
cursor.execute("SELECT * FROM profiles WHERE user_id = ?", (user_id,))
current = cursor.fetchone()
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
coins = data.get("coins", current["coins"] if current else 150)
level = data.get("level", current["level"] if current else 1)
max_unlocked = data.get("maxUnlockedLevel", current["max_unlocked_level"] if current else 1)
high_score = data.get("highScore", current["high_score"] if current else 0)
theme = data.get("theme", current["theme"] if current else "classic")
# Merge JSON dictionaries
def merge_json(key, default):
cur_obj = json.loads(current[key] or "{}") if current and current[key] else default
new_val = data.get(key)
if new_val is not None and isinstance(new_val, dict):
cur_obj.update(new_val)
return json.dumps(cur_obj)
upgrades_json = merge_json("upgrades", {})
materials_json = merge_json("materials", {})
plant_levels_json = merge_json("plant_levels", {}) if "plant_levels" in data else (json.dumps(data["plantLevels"]) if "plantLevels" in data else (current["plant_levels"] if current else "{}"))
perks_json = merge_json("perks", {})
zen_garden_json = json.dumps(data["zenGarden"]) if "zenGarden" in data else (current["zen_garden"] if current else "{}")
tree_json = json.dumps(data["treeOfWisdom"]) if "treeOfWisdom" in data else (current["tree_of_wisdom"] if current else "{}")
achievements_json = json.dumps(data["achievements"]) if "achievements" in data else (current["achievements"] if current else "{}")
if current:
cursor.execute("""
UPDATE profiles SET
coins = ?, level = ?, max_unlocked_level = ?, high_score = ?, theme = ?,
upgrades = ?, materials = ?, plant_levels = ?, perks = ?,
zen_garden = ?, tree_of_wisdom = ?, achievements = ?, updated_at = ?
WHERE user_id = ?
""", (
coins, level, max_unlocked, high_score, theme,
upgrades_json, materials_json, plant_levels_json, perks_json,
zen_garden_json, tree_json, achievements_json, now_str,
user_id
))
else:
cursor.execute("""
INSERT INTO profiles (
user_id, coins, level, max_unlocked_level, high_score, theme,
upgrades, materials, plant_levels, perks, zen_garden, tree_of_wisdom, achievements, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
user_id, coins, level, max_unlocked, high_score, theme,
upgrades_json, materials_json, plant_levels_json, perks_json,
zen_garden_json, tree_json, achievements_json, now_str
))
conn.commit()
conn.close()
return {"success": True}
def list_all_users():
conn = get_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT
u.id, u.username, u.created_at, u.last_login,
p.coins, p.level, p.max_unlocked_level, p.high_score, p.theme,
p.materials, p.plant_levels, p.perks, p.upgrades, p.zen_garden, p.tree_of_wisdom, p.achievements
FROM users u
LEFT JOIN profiles p ON u.id = p.user_id
ORDER BY u.id ASC
""")
rows = cursor.fetchall()
conn.close()
result = []
for r in rows:
result.append({
"id": r["id"],
"username": r["username"],
"createdAt": r["created_at"],
"lastLogin": r["last_login"],
"coins": r["coins"] if r["coins"] is not None else 150,
"level": r["level"] if r["level"] is not None else 1,
"maxUnlockedLevel": r["max_unlocked_level"] if r["max_unlocked_level"] is not None else 1,
"highScore": r["high_score"] if r["high_score"] is not None else 0,
"theme": r["theme"] or "classic",
"materials": json.loads(r["materials"] or "{}"),
"plantLevels": json.loads(r["plant_levels"] or "{}"),
"perks": json.loads(r["perks"] or "{}"),
"upgrades": json.loads(r["upgrades"] or "{}"),
"zenGarden": json.loads(r["zen_garden"] or "{}"),
"treeOfWisdom": json.loads(r["tree_of_wisdom"] or "{}"),
"achievements": json.loads(r["achievements"] or "{}")
})
return result
if __name__ == "__main__":
init_db()
users = list_all_users()
print(f"Database initialized. Total registered users: {len(users)}")