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

273 lines
10 KiB
Python

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())