""" Безопасное добавление уровней без удаления старых. Использует только 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())