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(filename): """Загружает слова из CSV файла без тем. Возвращает список слов и словарь переводов.""" words = [] translations = {} with open(filename, 'r', encoding='utf-8') as f: reader = csv.DictReader(f) for row in reader: word = row['polish'].strip().lower() if word and word not in words: words.append(word) translations[word] = { 'en': row.get('english', ''), 'ru': row.get('russian', ''), 'uk': row.get('ukrainian', '') } return words, translations def get_words_for_grid(all_words, num_words, grid_size): """Выбирает случайные слова подходящей длины.""" max_len = grid_size candidates = [w for w in all_words if len(w) <= max_len] if len(candidates) < num_words: candidates = all_words 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, all_words, used_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 = [w for w in all_words if 2 <= len(w) <= 4 and w not in used_words] 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, all_words, used_words, 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_used = set(words_sorted) | used_words filled_words, filled_paths = fill_empty_cells( grid, grid_size, occupied, all_words, all_used ) 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, level_name, grid, word_paths, words, translations): level = await conn.fetchrow( """INSERT INTO levels (level_number, grid_size, level, grid_letters, word_paths) VALUES ($1, $2, $3, $4, $5) RETURNING id""", level_num, grid_size, level_name, 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, level) VALUES ($1, $2, $3, $4, $5, $6)""", level_id, word, trans.get('ru', ''), trans.get('uk', ''), trans.get('en', ''), level_name ) async def main(): # Конфигурация уровней: 550 уровней на каждый уровень польского level_configs = [ { "name": "A1", "file": "words_a1.csv", "levels": [ (50, 4, 3), # 50 уровней: 4x4, 3 слова (100, 5, 4), # 100 уровней: 5x5, 4 слова (200, 6, 5), # 200 уровней: 6x6, 5 слов (200, 6, 6), # 200 уровней: 6x6, 6 слов ], }, { "name": "A2", "file": "words_a2.csv", "levels": [ (100, 6, 5), # 100 уровней: 6x6, 5 слов (150, 7, 6), # 150 уровней: 7x7, 6 слов (150, 8, 7), # 150 уровней: 8x8, 7 слов (150, 8, 8), # 150 уровней: 8x8, 8 слов ], }, { "name": "B1", "file": "words_b1.csv", "levels": [ (100, 8, 7), # 100 уровней: 8x8, 7 слов (150, 9, 8), # 150 уровней: 9x9, 8 слов (150, 10, 9), # 150 уровней: 10x10, 9 слов (150, 10, 10), # 150 уровней: 10x10, 10 слов ], }, ] conn = await asyncpg.connect(**DB_CONFIG) # Очищаем старые данные await conn.execute("DELETE FROM level_words") await conn.execute("DELETE FROM user_progress") await conn.execute("DELETE FROM sentence_progress") await conn.execute("DELETE FROM word_quiz_progress") await conn.execute("DELETE FROM bubble_words_progress") await conn.execute("DELETE FROM levels") await conn.execute("ALTER SEQUENCE levels_id_seq RESTART WITH 1") await conn.execute("UPDATE users SET last_completed_level = 0, current_level = 'A1'") total_generated = 0 for level_config in level_configs: level_name = level_config["name"] filename = level_config["file"] print(f"\n{'='*50}") print(f"Генерирую уровни для {level_name} из {filename}") print(f"{'='*50}") all_words, translations = load_words_from_csv(filename) print(f"Загружено слов: {len(all_words)}") if len(all_words) < 10: print(f"❌ Слишком мало слов в {filename}") continue used_words = set() level_num = 0 for count, grid_size, num_words in level_config["levels"]: for i in range(count): level_num += 1 for attempt in range(20): words = get_words_for_grid(all_words, num_words, grid_size) if len(words) < num_words: continue grid, word_paths, all_placed_words = generate_grid( words, grid_size, all_words, used_words ) if grid is not None: await save_level( conn, level_num, grid_size, level_name, grid, word_paths, all_placed_words, translations ) used_words.update(all_placed_words) total_generated += 1 print(f"✅ {level_name} Level {level_num}: {grid_size}x{grid_size}, {len(all_placed_words)} words") break else: print(f"❌ {level_name} Level {level_num}: FAILED after 20 attempts") await conn.close() print(f"\n🎉 Готово! Сгенерировано уровней: {total_generated}") if __name__ == "__main__": asyncio.run(main())