This site is developed to XHTML and CSS2 W3C standards.
If you see this paragraph, your browser does not support those standards and you
need to upgrade. Visit WaSP
for a variety of options.
Paste #675
Posted by: cross.py
Posted on: 2026-04-16 18:21:12
Age: 8 hrs ago
Views: 5
import tkinter as tk
from tkinter import messagebox, filedialog
class CrosswordApp:
def __init__(self):
self.root = tk.Tk()
self.root.title("Генератор кроссворда — центральное слово вертикально + легенда")
self.root.geometry("1250x820")
# Главный контейнер с Canvas и Scrollbar
self.main_canvas = tk.Canvas(self.root)
self.scrollbar = tk.Scrollbar(self.root, orient="vertical", command=self.main_canvas.yview)
self.scrollable_frame = tk.Frame(self.main_canvas)
self.scrollable_frame.bind(
"<Configure>",
lambda e: self.main_canvas.configure(scrollregion=self.main_canvas.bbox("all"))
)
self.main_canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
self.main_canvas.configure(yscrollcommand=self.scrollbar.set)
# Размещаем элементы
self.main_canvas.pack(side="left", fill="both", expand=True)
self.scrollbar.pack(side="right", fill="y")
# ====================== ВВОД ДАННЫХ ======================
input_frame = tk.Frame(self.scrollable_frame, padx=20, pady=15)
input_frame.pack(fill="x")
tk.Label(input_frame, text="Генератор кроссворда\n(центральное слово — вертикально по середине)",
font=("Arial", 18, "bold")).pack(pady=10)
# Центральное слово
tk.Label(input_frame, text="Центральное слово (будет ВЕРТИКАЛЬНЫМ):",
font=("Arial", 12, "bold")).pack(anchor="w", pady=(10, 0))
self.central_entry = tk.Entry(input_frame, width=85, font=("Arial", 14), justify="center")
self.central_entry.pack(pady=8)
tk.Button(input_frame, text="✅ Задать центральное слово",
command=self.set_central_word,
font=("Arial", 12), bg="#4CAF50", fg="white", height=1).pack(pady=5)
self.central_word = None
# Горизонтальные слова
tk.Label(input_frame, text="Горизонтальные слова (по одному на строку):",
font=("Arial", 12)).pack(anchor="w", pady=(15, 0))
self.other_words_text = tk.Text(input_frame, height=7, width=90, font=("Arial", 11))
self.other_words_text.pack(pady=5)
# Подсказки
tk.Label(input_frame, text="Подсказки (по одной на строку, в порядке горизонтальных слов):",
font=("Arial", 12)).pack(anchor="w", pady=(10, 0))
tk.Label(input_frame, text="Пример: Столица Франции или 1. По горизонтали: Столица Франции",
font=("Arial", 10), fg="gray").pack(anchor="w")
self.clues_text = tk.Text(input_frame, height=10, width=90, font=("Arial", 11))
self.clues_text.pack(pady=5)
# Кнопка генерации
tk.Button(input_frame, text="🚀 СГЕНЕРИРОВАТЬ КРОССВОРД",
command=self.generate_crossword,
font=("Arial", 15, "bold"), bg="#2196F3", fg="white", height=2).pack(pady=20)
# ====================== КНОПКИ ЭКСПОРТА ======================
export_frame = tk.Frame(self.scrollable_frame, pady=10)
export_frame.pack()
self.export_filled_btn = tk.Button(
export_frame,
text="💾 Экспорт ЗАПОЛНЕННОГО SVG (с буквами + легенда)",
command=lambda: self.export_svg(filled=True),
font=("Arial", 12), bg="#FF9800", fg="white", state="disabled", height=2
)
self.export_filled_btn.pack(side="left", padx=15)
self.export_puzzle_btn = tk.Button(
export_frame,
text="💾 Экспорт ПУСТОГО SVG (подсказки, стрелки + легенда)",
command=lambda: self.export_svg(filled=False),
font=("Arial", 12), bg="#9C27B0", fg="white", state="disabled", height=2
)
self.export_puzzle_btn.pack(side="left", padx=15)
# ====================== ОБЛАСТЬ ПРЕДПРОСМОТРА ======================
self.preview_frame = tk.Frame(self.scrollable_frame, bg="#f5f5f5", pady=15)
self.preview_frame.pack(fill="both", expand=True, padx=20, pady=10)
self.canvas = None
self.shifted_grid = None
self.grid_height = 0
self.grid_width = 0
self.word_placements = None
self.clues_list = None
# Привязка колесика мыши к скроллу
self.main_canvas.bind_all("<MouseWheel>", self._on_mousewheel)
self.root.mainloop()
def _on_mousewheel(self, event):
self.main_canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def set_central_word(self):
word = self.central_entry.get().strip().upper().replace(" ", "")
if not word or not word.isalpha():
messagebox.showerror("Ошибка", "Введите корректное центральное слово (только буквы)!")
return
self.central_word = word
messagebox.showinfo("Готово", f"Центральное слово сохранено:\n{word}\nОно будет размещено вертикально по середине.")
def generate_crossword(self):
if not self.central_word:
messagebox.showerror("Ошибка", "Сначала задайте центральное слово!")
return
# Получаем горизонтальные слова
other_text = self.other_words_text.get("1.0", tk.END).strip()
other_words = [line.strip().upper().replace(" ", "")
for line in other_text.splitlines()
if line.strip() and line.strip().isalpha()]
if not other_words:
messagebox.showerror("Ошибка", "Введите хотя бы одно горизонтальное слово!")
return
# Получаем подсказки
clues_raw = self.clues_text.get("1.0", tk.END).strip().splitlines()
self.clues_list = []
for line in clues_raw:
clue = line.strip()
if not clue:
continue
# Убираем номера в начале
if clue[0].isdigit():
for i, ch in enumerate(clue):
if ch.isalpha() or ch == '«' or ch == '"':
self.clues_list.append(clue[i:].strip())
break
else:
self.clues_list.append(clue)
# ====================== ПОСТРОЕНИЕ КРОССВОРДА ======================
central_list = list(self.central_word)
n_rows_central = len(central_list)
placements = {}
used_rows = set()
placed = []
not_placed = []
for word in other_words:
found = False
for row_idx in range(n_rows_central):
if row_idx in used_rows:
continue
for idx in range(len(word)):
if word[idx] == central_list[row_idx]:
placements[row_idx] = (word, idx)
used_rows.add(row_idx)
placed.append(word)
found = True
break
if found:
break
if not found:
not_placed.append(word)
# Построение сетки
grid = {}
word_placements = []
mid_col = 12
central_start_row = 8
# Центральное слово (вертикально)
for i, letter in enumerate(central_list):
r = central_start_row + i
c = mid_col
grid[(r, c)] = letter
word_placements.append({
'dir': 'down',
'start_r': central_start_row,
'start_c': mid_col,
'word': self.central_word,
'clue': "Центральное слово (вертикально)"
})
# Горизонтальные слова
clue_idx = 0
for central_r_idx, (word, match_idx) in placements.items():
r = central_start_row + central_r_idx
start_c = mid_col - match_idx
for j, letter in enumerate(word):
c = start_c + j
grid[(r, c)] = letter
clue = self.clues_list[clue_idx] if clue_idx < len(self.clues_list) else f"Слово: {word}"
word_placements.append({
'dir': 'across',
'start_r': r,
'start_c': start_c,
'word': word,
'clue': clue
})
clue_idx += 1
# Сдвиг координат
rows_list = [r for r, c in grid.keys()]
cols_list = [c for r, c in grid.keys()]
min_r = min(rows_list)
min_c = min(cols_list)
shift_r = -min_r + 4
shift_c = -min_c + 4
self.shifted_grid = {(r + shift_r, c + shift_c): letter for (r, c), letter in grid.items()}
self.grid_height = (max(rows_list) - min_r + 9)
self.grid_width = (max(cols_list) - min_c + 9)
for p in word_placements:
p['start_r'] += shift_r
p['start_c'] += shift_c
# Нумерация
word_placements.sort(key=lambda x: (x['start_r'], x['start_c']))
for num, p in enumerate(word_placements, 1):
p['number'] = num
self.word_placements = word_placements
# ====================== ОТРИСОВКА ======================
if self.canvas:
self.canvas.destroy()
cell_size = 48
canvas_w = self.grid_width * cell_size + 40
canvas_h = self.grid_height * cell_size + 40
self.canvas = tk.Canvas(self.preview_frame, width=canvas_w, height=canvas_h,
bg="#eeeeee", highlightthickness=0)
self.canvas.pack(pady=10)
for row in range(self.grid_height):
for col in range(self.grid_width):
x1 = col * cell_size + 20
y1 = row * cell_size + 20
x2 = x1 + cell_size
y2 = y1 + cell_size
key = (row, col)
if key in self.shifted_grid:
self.canvas.create_rectangle(x1, y1, x2, y2, fill="#ffffff", outline="#333333", width=3)
self.canvas.create_text((x1 + x2)//2, (y1 + y2)//2,
text=self.shifted_grid[key],
font=("Arial", 24, "bold"), fill="#1a1a1a")
else:
self.canvas.create_rectangle(x1, y1, x2, y2, fill="#1a1a1a", outline="#1a1a1a")
# Активация кнопок экспорта
self.export_filled_btn.config(state="normal")
self.export_puzzle_btn.config(state="normal")
messagebox.showinfo("Готово!",
f"Кроссворд сгенерирован!\n"
f"Центральное слово: {self.central_word}\n"
f"Размещено горизонтальных слов: {len(placed)}")
def export_svg(self, filled=True):
if not self.shifted_grid:
messagebox.showerror("Ошибка", "Сначала сгенерируйте кроссворд!")
return
default_name = f"кроссворд_{self.central_word}_{'filled' if filled else 'puzzle'}.svg"
filename = filedialog.asksaveasfilename(
defaultextension=".svg",
filetypes=[("SVG файлы", "*.svg")],
initialfile=default_name
)
if not filename:
return
cell_size = 65
padding = 60
grid_w = self.grid_width * cell_size
grid_h = self.grid_height * cell_size
legend_x = grid_w + padding + 100
total_w = legend_x + 520
total_h = max(grid_h + 120, 900)
svg = f'''<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="{total_w}" height="{total_h}" viewBox="0 0 {total_w} {total_h}">
<defs>
<style>
text {{ font-family: Arial, sans-serif; }}
.letter {{ font-size: 38px; font-weight: bold; fill: #111; }}
.number {{ font-size: 19px; font-weight: bold; fill: #000; }}
.clue {{ font-size: 21px; fill: #222; }}
.title {{ font-size: 26px; font-weight: bold; fill: #000; }}
</style>
</defs>
'''
# Сетка
for row in range(self.grid_height):
for col in range(self.grid_width):
x = padding + col * cell_size
y = padding + row * cell_size
key = (row, col)
if key in self.shifted_grid:
svg += f' <rect x="{x}" y="{y}" width="{cell_size}" height="{cell_size}" fill="#ffffff" stroke="#333" stroke-width="5"/>\n'
if filled:
letter = self.shifted_grid[key]
svg += f' <text x="{x + cell_size/2}" y="{y + cell_size/2 + 12}" text-anchor="middle" dominant-baseline="middle" class="letter">{letter}</text>\n'
start_info = next((p for p in self.word_placements if p.get('start_r') == row and p.get('start_c') == col), None)
if start_info:
num = start_info['number']
svg += f' <text x="{x + 8}" y="{y + 26}" class="number">{num}</text>\n'
if not filled:
arrow = "→" if start_info['dir'] == "across" else "↓"
svg += f' <text x="{x + cell_size - 25}" y="{y + cell_size - 15}" font-size="34" fill="#666" text-anchor="end">{arrow}</text>\n'
else:
svg += f' <rect x="{x}" y="{y}" width="{cell_size}" height="{cell_size}" fill="#1a1a1a" stroke="#1a1a1a"/>\n'
# Легенда
y = padding + 40
svg += f' <text x="{legend_x}" y="{y}" class="title">ПО ГОРИЗОНТАЛИ (Across)</text>\n'
y += 60
for p in self.word_placements:
if p['dir'] == 'across':
svg += f' <text x="{legend_x}" y="{y}" class="clue">{p["number"]}. {p["clue"]}</text>\n'
y += 38
y += 50
svg += f' <text x="{legend_x}" y="{y}" class="title">ПО ВЕРТИКАЛИ (Down)</text>\n'
y += 55
for p in self.word_placements:
if p['dir'] == 'down':
svg += f' <text x="{legend_x}" y="{y}" class="clue">{p["number"]}. {p["clue"]}</text>\n'
y += 38
svg += '</svg>'
try:
with open(filename, "w", encoding="utf-8") as f:
f.write(svg)
mode_text = "ЗАПОЛНЕННЫЙ (с буквами)" if filled else "ПУСТОЙ (с подсказками, стрелками и легендой)"
messagebox.showinfo("Успешно!", f"SVG-файл сохранён:\n{filename}\n\nРежим: {mode_text}")
except Exception as e:
messagebox.showerror("Ошибка сохранения", str(e))
if __name__ == "__main__":
app = CrosswordApp()
Download raw |
Create new paste