Files
filwordspl-backend/add_levels.py
T
2026-09-20 11:45:58 +00:00

259 lines
8.7 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.
"""
Безопасное добавление уровней без удаления старых.
Использует только words.csv.
"""
import asyncio
import asyncpg
import json
import random
import csv
from config import DB_CONFIG
DIRECTIONS = [(-1,0),(0,-1),(0,1),(1,0)]
ALPHABET = "AĄBCĆDEĘFGHIJKLŁMNŃOÓPRSŚTUWYZŹŻ"
def load_words_from_csv(filenames):
words_by_theme = {}
translations = {}
for filename in filenames:
with open(filename, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
theme = row['theme']
word = row['polish'].lower()
if theme not in words_by_theme:
words_by_theme[theme] = []
if word not in words_by_theme[theme]:
words_by_theme[theme].append(word)
translations[word] = {
'en': row['english'],
'ru': row['russian'],
'uk': row['ukrainian']
}
return words_by_theme, translations
def get_words_for_grid(words_by_theme, theme, num_words, grid_size):
max_len = grid_size
candidates = [w for w in words_by_theme[theme] if len(w) <= max_len]
if len(candidates) < num_words:
candidates = words_by_theme[theme]
random.shuffle(candidates)
return candidates[:num_words]
def try_place_word(word, grid_size, occupied, max_attempts=500):
word = word.upper()
for _ in range(max_attempts):
start = (random.randrange(grid_size), random.randrange(grid_size))
if start in occupied:
continue
path = [start]
visited = {start}
ok = True
for _ in range(len(word) - 1):
r, c = path[-1]
candidates = []
for dr, dc in DIRECTIONS:
nr, nc = r + dr, c + dc
if 0 <= nr < grid_size and 0 <= nc < grid_size:
if (nr, nc) not in occupied and (nr, nc) not in visited:
candidates.append((nr, nc))
if not candidates:
ok = False
break
nxt = random.choice(candidates)
path.append(nxt)
visited.add(nxt)
if ok and len(path) == len(word):
return path
return None
def fill_empty_cells(grid, grid_size, occupied, words_by_theme, all_words):
empty_cells = set()
for r in range(grid_size):
for c in range(grid_size):
if (r, c) not in occupied:
empty_cells.add((r, c))
if not empty_cells:
return [], {}
filler_words = []
for theme in words_by_theme:
for w in words_by_theme[theme]:
if 2 <= len(w) <= 4 and w not in all_words:
filler_words.append(w)
filler_words = sorted(list(set(filler_words)), key=len, reverse=True)
random.shuffle(filler_words)
filled_paths = {}
filled_words = []
remaining_empty = empty_cells.copy()
for word in filler_words:
if len(word) > len(remaining_empty):
continue
for _ in range(500):
if not remaining_empty:
break
start = random.choice(list(remaining_empty))
path = [start]
visited = {start}
ok = True
for _ in range(len(word) - 1):
r, c = path[-1]
candidates = []
for dr, dc in DIRECTIONS:
nr, nc = r + dr, c + dc
if 0 <= nr < grid_size and 0 <= nc < grid_size:
if (nr, nc) in remaining_empty and (nr, nc) not in visited:
candidates.append((nr, nc))
if not candidates:
ok = False
break
nxt = random.choice(candidates)
path.append(nxt)
visited.add(nxt)
if ok and len(path) == len(word):
for (r, c), letter in zip(path, word.upper()):
grid[r][c] = letter
if (r, c) in remaining_empty:
remaining_empty.remove((r, c))
filled_paths[word] = [list(p) for p in path]
filled_words.append(word)
break
for r, c in remaining_empty:
grid[r][c] = random.choice(ALPHABET)
return filled_words, filled_paths
def generate_grid(words, grid_size, words_by_theme, max_restarts=200):
words_sorted = sorted(words, key=len, reverse=True)
for _ in range(max_restarts):
grid = [['' for _ in range(grid_size)] for _ in range(grid_size)]
occupied = set()
word_paths = {}
success = True
for word in words_sorted:
path = try_place_word(word, grid_size, occupied)
if path is None:
success = False
break
for (r, c), letter in zip(path, word.upper()):
grid[r][c] = letter
occupied.update(path)
word_paths[word] = [list(p) for p in path]
if not success:
continue
all_words = set(words_sorted)
filled_words, filled_paths = fill_empty_cells(
grid, grid_size, occupied, words_by_theme, all_words
)
word_paths.update(filled_paths)
all_placed_words = words_sorted + filled_words
return grid, word_paths, all_placed_words
return None, None, None
async def save_level(conn, level_num, grid_size, theme, grid, word_paths, words, translations):
level = await conn.fetchrow(
"""INSERT INTO levels (level_number, grid_size, theme, grid_letters, word_paths)
VALUES ($1, $2, $3, $4, $5) RETURNING id""",
level_num, grid_size, theme, json.dumps(grid), json.dumps(word_paths)
)
level_id = level['id']
for word in words:
trans = translations.get(word, {})
await conn.execute(
"""INSERT INTO level_words (level_id, word_pl, translation_ru, translation_uk, translation_en)
VALUES ($1, $2, $3, $4, $5)""",
level_id, word,
trans.get('ru', ''),
trans.get('uk', ''),
trans.get('en', '')
)
async def main():
# ============ НАСТРОЙКИ ============
words_by_theme, translations = load_words_from_csv([
'words.csv'
])
NEW_LEVEL_CONFIG = [
(25, 10, 9), # 25 уровней: 10x10, 9 слов
(25, 10, 10), # 25 уровней: 10x10, 10 слов
(25, 10, 11), # 25 уровней: 10x10, 11 слов
(25, 10, 12), # 25 уровней: 10x10, 12 слов
]
# ==================================
conn = await asyncpg.connect(**DB_CONFIG)
current_max = await conn.fetchval("SELECT COALESCE(MAX(level_number), 0) FROM levels")
print(f"Сейчас уровней в базе: {current_max}")
print(f"Добавляем уровни {current_max + 1}{current_max + 100}")
themes = list(words_by_theme.keys())
random.shuffle(themes)
level_num = current_max
theme_idx = 0
total_generated = 0
for count, grid_size, num_words in NEW_LEVEL_CONFIG:
for i in range(count):
level_num += 1
for attempt in range(15):
theme = themes[theme_idx % len(themes)]
theme_idx += 1
words = get_words_for_grid(words_by_theme, theme, num_words, grid_size)
if len(words) < num_words:
continue
grid, word_paths, all_words = generate_grid(
words, grid_size, words_by_theme
)
if grid is not None:
await save_level(
conn, level_num, grid_size, theme,
grid, word_paths, all_words, translations
)
total_cells = grid_size * grid_size
filled_cells = sum(len(w) for w in all_words)
print(f"✅ Level {level_num}: {grid_size}x{grid_size}, "
f"theme={theme}, words={len(all_words)} ({num_words} main), "
f"fill={filled_cells}/{total_cells}")
total_generated += 1
break
else:
print(f"❌ FAILED: Level {level_num} after 15 attempts")
level_num -= 1
await conn.close()
print(f"\n🎉 Готово! Добавлено {total_generated} новых уровней. Теперь всего уровней: {level_num}")
if __name__ == "__main__":
asyncio.run(main())