stepstead-backend/db/seed_world_graph.py
jts ea049b8ccd İlk commit: Stepstead backend (FastAPI)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:30:24 +03:00

249 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Örnek dünya: 15 lokasyon + 21 yol + lokasyon kaynakları + 12 D&D olayı.
Idempotent (code üzerinden upsert). Harita koordinatları 0-1000 (statik görsel için).
Yeni ham maddeler (13 odun, cevherler, otlar, balıklar, av) lokasyonlara tier'a göre dağıtılır.
Çalıştırma: cd /opt/stepstead && ./venv/bin/python3 db/seed_world_graph.py
"""
import asyncio
import json
import sys
import asyncpg
sys.path.insert(0, "/opt/stepstead")
from config import settings
DSN = settings.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://")
# (code, ad, tür, açıklama, x, y)
LOCATIONS = [
("koy", "Stepstead Köyü", "village", "Yolculuğun başladığı huzurlu köy. Etrafta taş, sopa ve sarmaşık bulunur.", 163, 186),
("pazar", "Pazarköy", "city", "Tüccarların uğrak yeri, canlı bir kasaba.", 232, 392),
("liman", "Liman Kenti", "city", "Büyük gemilerin yanaştığı ticaret limanı. Derin deniz balıılığı buradan yapılır.", 140, 630),
("fisilti_ormani", "Fısıltı Ormanı", "forest", "Köye en yakın orman. Meşe ve çam ağaçları.", 205, 110),
("gumus_camlik", "Gümüş Çamlık", "forest", "Sisli tepelerde huş ve kayın ağaçları.", 110, 300),
("akca_koru", "Akça Koru", "forest", "Akçaağaç ve söğütleriyle ünlü sakin koru.", 282, 255),
("kadim_orman", "Kadim Orman", "forest", "Yüzyıllık dişbudak, sedir ve ceviz ağaçları.", 320, 468),
("karanlik_orman", "Karaağaç Karanlığı", "forest", "En değerli ağaçların büyüdüğü tekinsiz orman: porsuk, maun, abanoz...", 262, 600),
("tas_ocagi", "Taş Ocağı", "mine", "Bakır ve kalay damarları, bol kömür.", 610, 290),
("derin_maden", "Derin Maden", "mine", "Demir ve gümüş çıkarılan eski maden.", 750, 320),
("ejder_dagi", "Ejder Dağı", "mine", "Altın damarlarının saklandığı efsanevi zirve.", 790, 115),
("aynali_gol", "Aynalı Göl", "lake", "Sazan ve alabalığın bol olduğu berrak göl.", 723, 655),
("firtina_kiyisi", "Fırtına Kıyısı", "lake", "Dalgalı denizde uskumru, palamut ve lüfer.", 685, 905),
("cayirlar", "Uçsuz Çayırlar", "meadow", "Şifalı otların yetiştiği rüzgârlı çayırlar.", 590, 760),
("av_topraklari", "Av Toprakları", "hunting", "Tavşan ve geyik avlanır. Daha derinde kurt vadisi.", 855, 712),
]
# (a, b, adım, olay_şansı)
ROADS = [
("koy", "fisilti_ormani", 320, 0.30),
("koy", "tas_ocagi", 350, 0.30),
("koy", "aynali_gol", 380, 0.25),
("koy", "cayirlar", 400, 0.30),
("koy", "pazar", 450, 0.20),
("fisilti_ormani", "gumus_camlik", 400, 0.35),
("gumus_camlik", "akca_koru", 450, 0.35),
("akca_koru", "kadim_orman", 500, 0.40),
("kadim_orman", "karanlik_orman", 600, 0.50),
("gumus_camlik", "av_topraklari", 500, 0.40),
("av_topraklari", "karanlik_orman", 650, 0.50),
("tas_ocagi", "derin_maden", 450, 0.35),
("derin_maden", "ejder_dagi", 600, 0.45),
("aynali_gol", "pazar", 300, 0.20),
("pazar", "liman", 500, 0.25),
("liman", "firtina_kiyisi", 250, 0.25),
("aynali_gol", "firtina_kiyisi", 550, 0.35),
("cayirlar", "av_topraklari", 450, 0.35),
("cayirlar", "gumus_camlik", 350, 0.30),
("derin_maden", "liman", 550, 0.30),
("cayirlar", "tas_ocagi", 300, 0.25),
]
# lokasyon → [(item_code, skill, adım, xp, cooldown_s)]
RESOURCES = {
"koy": [
("stick", "foraging", 4, 3, 120), ("stone_small", "foraging", 5, 3, 120),
("vine", "foraging", 6, 4, 120), ("herb_mint", "foraging", 8, 6, 180),
],
"fisilti_ormani": [
("log_oak", "woodcutting", 12, 8, 240), ("log_pine", "woodcutting", 14, 10, 240),
("stick", "foraging", 4, 3, 120), ("vine", "foraging", 6, 4, 120),
("herb_sage", "foraging", 9, 7, 180),
],
"gumus_camlik": [
("log_birch", "woodcutting", 16, 12, 240), ("log_beech", "woodcutting", 18, 14, 240),
("herb_thyme", "foraging", 10, 8, 180), ("stick", "foraging", 4, 3, 120),
],
"akca_koru": [
("log_maple", "woodcutting", 20, 16, 300), ("log_willow", "woodcutting", 22, 18, 300),
("herb_chamomile", "foraging", 11, 9, 180), ("vine", "foraging", 6, 4, 120),
],
"kadim_orman": [
("log_ash", "woodcutting", 24, 20, 300), ("log_cedar", "woodcutting", 26, 22, 300),
("log_walnut", "woodcutting", 28, 24, 300), ("herb_nettle", "foraging", 12, 10, 180),
],
"karanlik_orman": [
("log_cherry", "woodcutting", 30, 26, 360), ("log_yew", "woodcutting", 34, 30, 360),
("log_mahogany", "woodcutting", 38, 34, 360), ("log_ebony", "woodcutting", 42, 38, 360),
("herb_ginseng", "foraging", 16, 14, 240), ("herb_saffron", "foraging", 18, 16, 240),
],
"tas_ocagi": [
("ore_copper", "mining", 15, 10, 240), ("ore_tin", "mining", 15, 10, 240),
("coal", "mining", 12, 8, 240), ("stone_small", "foraging", 5, 3, 120),
],
"derin_maden": [
("ore_iron", "mining", 20, 16, 300), ("coal", "mining", 12, 8, 240),
("ore_silver", "mining", 26, 22, 300),
],
"ejder_dagi": [
("ore_gold", "mining", 34, 30, 360), ("ore_silver", "mining", 26, 22, 300),
("ore_iron", "mining", 20, 16, 300),
],
"aynali_gol": [
("fish_carp", "fishing", 14, 9, 240), ("fish_perch", "fishing", 15, 10, 240),
("fish_trout", "fishing", 18, 14, 240), ("vine", "foraging", 6, 4, 120),
],
"firtina_kiyisi": [
("fish_anchovy", "fishing", 14, 9, 240), ("fish_mackerel", "fishing", 16, 12, 240),
("fish_bonito", "fishing", 18, 14, 240), ("fish_bluefish", "fishing", 20, 16, 240),
("fish_bream", "fishing", 20, 16, 240),
],
"liman": [
("fish_pike", "fishing", 24, 20, 300), ("fish_catfish", "fishing", 26, 22, 300),
("fish_salmon", "fishing", 28, 24, 300), ("fish_turbot", "fishing", 30, 26, 300),
("fish_tuna", "fishing", 34, 30, 360), ("fish_sturgeon", "fishing", 38, 34, 360),
],
"cayirlar": [
("herb_rosemary", "foraging", 10, 8, 180), ("herb_basil", "foraging", 10, 8, 180),
("herb_lemonbalm", "foraging", 12, 10, 180), ("herb_fennel", "foraging", 12, 10, 180),
("herb_lavender", "foraging", 14, 12, 180), ("stick", "foraging", 4, 3, 120),
],
"av_topraklari": [
("meat_rabbit", "hunting", 16, 12, 300), ("leather_rabbit", "hunting", 16, 12, 300),
("meat_deer", "hunting", 20, 16, 300), ("leather_deer", "hunting", 20, 16, 300),
("meat_boar", "hunting", 24, 20, 360), ("leather_boar", "hunting", 24, 20, 360),
("meat_wolf", "hunting", 28, 24, 360), ("leather_wolf", "hunting", 28, 24, 360),
("meat_bear", "hunting", 34, 30, 420), ("leather_bear", "hunting", 34, 30, 420),
],
"pazar": [
("herb_mint", "foraging", 8, 6, 180), ("stick", "foraging", 4, 3, 120),
],
}
# D&D olay kartları. choices: skill check'li {text, skill, dc, success{}, fail{}} ya da düz {text, outcome{}}
EVENTS = [
("gezgin_tacir", "Gezgin Tacir", "Yolda rengarenk arabasıyla bir tacirle karşılaşıyorsun. Sana el sallıyor.", [
{"text": "Selam ver ve sohbet et", "outcome": {"text": "Tacir sohbetten memnun, sana bir nane çayı ikram ediyor.", "items": [{"code": "tea_mint", "qty": 1}]}},
{"text": "Başını öne eğip yoluna devam et", "outcome": {"text": "Tacir omuz silkip uzaklaşıyor."}},
]),
("haydut_pususu", "Haydut Pususu", "Çalıların arasından iki haydut fırlıyor! 'Adımlarını ya da canını!'", [
{"text": "Savaş!", "skill": "combat", "dc": 12,
"success": {"text": "Haydutları püskürttün! Kaçarken düşürdükleri keseyi aldın.", "items": [{"code": "stone_small", "qty": 3}], "xp": {"skill": "combat", "amount": 25}},
"fail": {"text": "Dayak yedin ve kaçtın. Yorgunluktan adımların eridi.", "steps": -150}},
{"text": "Kaçmayı dene", "skill": "hunting", "dc": 10,
"success": {"text": "Çevik adımlarla izini kaybettirdin.", "xp": {"skill": "hunting", "amount": 15}},
"fail": {"text": "Yakalandın, keseni silkelediler.", "steps": -100}},
{"text": "Adımlarından 100 ver", "outcome": {"text": "Haydutlar parayı alıp gözden kayboldu.", "steps": -100}},
]),
("yarali_gezgin", "Yaralı Gezgin", "Yol kenarında ayağı burkulmuş bir gezgin oturuyor, yardım istiyor.", [
{"text": "Yarasını sar (Simya)", "skill": "alchemy", "dc": 8,
"success": {"text": "Gezgin minnetle teşekkür edip çantasından ot uzatıyor.", "items": [{"code": "herb_mint", "qty": 2}], "xp": {"skill": "alchemy", "amount": 20}},
"fail": {"text": "Elinden geleni yaptın ama pek işe yaramadı. Zaman kaybettin.", "steps": -30}},
{"text": "Görmezden gel", "outcome": {"text": "Vicdanın sızlasa da yoluna devam ettin."}},
]),
("kayip_sandik", "Kayıp Sandık", "Patikanın kenarında yarı gömülü eski bir sandık görüyorsun.", [
{"text": "Kazıp aç", "outcome": {"text": "İçinden işe yarar malzemeler çıktı!", "items": [{"code": "stone_small", "qty": 2}, {"code": "vine", "qty": 2}]}},
{"text": "Tuzak olabilir, dokunma", "outcome": {"text": "Temkinli davranıp uzaklaştın."}},
]),
("dev_ayi", "Dev Ayı", "Önünde iki ayağı üzerinde doğrulmuş kocaman bir ayı! Homurtusu yeri titretiyor.", [
{"text": "Savaş!", "skill": "combat", "dc": 15,
"success": {"text": "Zorlu bir mücadele sonunda ayıyı alt ettin!", "items": [{"code": "meat_bear", "qty": 1}, {"code": "leather_bear", "qty": 1}], "xp": {"skill": "combat", "amount": 40}},
"fail": {"text": "Ayı seni fena hırpaladı. Canını zor kurtardın.", "steps": -200}},
{"text": "Yavaşça geri çekil", "skill": "hunting", "dc": 8,
"success": {"text": "Ses çıkarmadan uzaklaştın.", "xp": {"skill": "hunting", "amount": 10}},
"fail": {"text": "Bir dala bastın! Ayı kovaladı, koşmaktan tükendin.", "steps": -120}},
]),
("mantar_halkasi", "Mantar Halkası", "Bir açıklıkta mükemmel daire çizen mantarlar... Ortasında bir şey parlıyor.", [
{"text": "Dikkatlice topla (Toplama)", "skill": "foraging", "dc": 10,
"success": {"text": "Halkanın ortasından nadir otlar topladın!", "items": [{"code": "herb_chamomile", "qty": 2}], "xp": {"skill": "foraging", "amount": 20}},
"fail": {"text": "Mantarlar zehirliymiş, başın döndü.", "steps": -60}},
{"text": "Uğursuzluktur, dokunma", "outcome": {"text": "Halkaya saygıyla selam verip geçtin."}},
]),
("eski_harabe", "Eski Harabe", "Sarmaşıklarla kaplı yıkık bir taş yapı. İçinden serin bir esinti geliyor.", [
{"text": "İçini araştır (İnşaat)", "skill": "construction", "dc": 12,
"success": {"text": "Sağlam taşlar ve eski aletler buldun!", "items": [{"code": "stone_small", "qty": 4}], "xp": {"skill": "construction", "amount": 25}},
"fail": {"text": "Bir taş kaydı, az daha altında kalıyordun. Hızla çıktın.", "steps": -80}},
{"text": "Dışarıdan bak, devam et", "outcome": {"text": "Harabeyi haritana not edip ilerledin."}},
]),
("nehir_gecidi", "Nehir Geçidi", "Yol taşkın bir dereyle kesilmiş. Sığ bir geçit var ama akıntı hızlı.", [
{"text": "Geçerken balık da yakala (Balıılık)", "skill": "fishing", "dc": 8,
"success": {"text": "Hem karşıya geçtin hem eline bir sazan takıldı!", "items": [{"code": "fish_carp", "qty": 1}], "xp": {"skill": "fishing", "amount": 15}},
"fail": {"text": "Ayağın kaydı, sırılsıklam oldun.", "steps": -40}},
{"text": "Uzun ama güvenli yoldan dolaş", "outcome": {"text": "Ekstra yürüdün ama kuru kaldın.", "steps": -50}},
]),
("gizemli_yasli", "Gizemli Yaşlı", "Bastonlu bir ihtiyar taşın üstünde oturmuş, sana bakıp gülümsüyor: 'Otların dilinden anlar mısın evlat?'", [
{"text": "Yanına otur ve dinle", "outcome": {"text": "Sana otların sırlarını anlattı. Bilgeleştin.", "xp": {"skill": "alchemy", "amount": 20}}},
{"text": "Kibarca reddet", "outcome": {"text": "İhtiyar gülümseyip gözlerini kapadı."}},
]),
("kirik_araba", "Kırık Araba", "Bir çiftçinin arabasının tekeri kırılmış, yükü yola saçılmış.", [
{"text": "Tamir et (İnşaat)", "skill": "construction", "dc": 10,
"success": {"text": "Tekeri taktın! Çiftçi teşekkür olarak sarmaşık ipi verdi.", "items": [{"code": "vine", "qty": 3}], "xp": {"skill": "construction", "amount": 20}},
"fail": {"text": "Uğraştın ama beceremedin. Çiftçi yine de teşekkür etti.", "steps": -30}},
{"text": "Yardım edemem, acelem var", "outcome": {"text": "Çiftçi arkandan söylendi."}},
]),
("saganak", "Sağanak", "Gökyüzü kararıyor, bardaktan boşanırcasına yağmur başlıyor!", [
{"text": "Hızlıca sığınak yap (İnşaat)", "skill": "construction", "dc": 8,
"success": {"text": "Dallardan derme çatma ama işlevsel bir sığınak! Yağmuru kuru geçirdin.", "xp": {"skill": "construction", "amount": 15}},
"fail": {"text": "Sığınak çöktü, iliklerine kadar ıslandın.", "steps": -60}},
{"text": "Yağmurda yürümeye devam et", "outcome": {"text": "Islandın, üşüdün, yoruldun ama yol aldın.", "steps": -30}},
]),
("sansli_bulus", "Şanslı Buluş", "Patikanın hemen kenarında birinin unuttuğu küçük bir malzeme yığını duruyor.", [
{"text": "Topla", "outcome": {"text": "Sopa ve taşları sırt çantana koydun.", "items": [{"code": "stick", "qty": 2}, {"code": "stone_small", "qty": 2}]}},
{"text": "Sahibi gelir, bırak", "outcome": {"text": "Dürüstlüğünle gurur duyarak ilerledin."}},
]),
]
async def main():
conn = await asyncpg.connect(DSN)
for code, name, typ, desc, x, y in LOCATIONS:
await conn.execute("""
INSERT INTO locations (code, name, type, description, map_x, map_y)
VALUES ($1,$2,$3,$4,$5,$6)
ON CONFLICT (code) DO UPDATE SET name=$2, type=$3, description=$4, map_x=$5, map_y=$6
""", code, name, typ, desc, x, y)
ids = {r["code"]: r["id"] for r in await conn.fetch("SELECT id, code FROM locations")}
for a, b, cost, chance in ROADS:
await conn.execute("""
INSERT INTO roads (loc_a, loc_b, step_cost, event_chance)
VALUES ($1,$2,$3,$4)
ON CONFLICT (loc_a, loc_b) DO UPDATE SET step_cost=$3, event_chance=$4
""", ids[a], ids[b], cost, chance)
n_res = 0
for loc, res in RESOURCES.items():
for item, skill, steps, xp, cd in res:
await conn.execute("""
INSERT INTO location_resources (location_id, item_code, skill, step_cost, xp, cooldown_s)
VALUES ($1,$2,$3,$4,$5,$6)
ON CONFLICT (location_id, item_code) DO UPDATE SET skill=$3, step_cost=$4, xp=$5, cooldown_s=$6
""", ids[loc], item, skill, steps, xp, cd)
n_res += 1
for code, name, txt, choices in EVENTS:
await conn.execute("""
INSERT INTO events (code, name, text, choices) VALUES ($1,$2,$3,$4::jsonb)
ON CONFLICT (code) DO UPDATE SET name=$2, text=$3, choices=$4::jsonb
""", code, name, txt, json.dumps(choices))
# Konumu olmayan karakterleri köye koy
moved = await conn.execute(
"UPDATE characters SET location_id=$1 WHERE location_id IS NULL", ids["koy"])
print(f"Lokasyon: {len(LOCATIONS)}, yol: {len(ROADS)}, kaynak: {n_res}, olay: {len(EVENTS)}. Karakter taşındı: {moved}")
await conn.close()
asyncio.run(main())