Команда Python for Devs подготовила перевод статьи Элa Свейгарта о неудачных экспериментах с vibe coding. Все говорят, что ИИ уже умеет писать приложения, но стоит чуть отклониться от привычных сценариев — и всё идёт наперекосяк. Картофельная Африка вместо карты, пинбол, превращающийся в пинг-понг, и счёты с отрицательными числами — автор собрал коллекцию своих провалов с vibe coding.
Последнюю неделю я экспериментировал с vibe coding: просил LLM-модели вроде ChatGPT, Claude и Gemini написать полноценные приложения так, будто у меня нет вообще никаких навыков программирования. LLM легко решают задачи на кодинг или собеседовательные вопросы. Но мне хотелось проверить, насколько далеко они способны зайти, если попросить их создать готовые приложения, и какие типичные сбои при этом проявляются. В роли «непрограммиста» я мог бы исправлять баги только описывая их LLM. Для простоты я выбрал небольшие приложения на Python, использующие только стандартную библиотеку и пакет tkinter
для GUI. В этом посте я рассказываю об этих провалах — о тех случаях, где ИИ просто не справляется.
Меня не волнует изысканный или красивый интерфейс (в конце концов, тут всё ограничено tkinter). Важно понять, запускается ли приложение без серьёзных ошибок. Для этих экспериментов я использовал ChatGPT 5, Gemini 2.5 Pro и Claude Sonnet 4.
В тексте я привожу исходный код некоторых программ, сгенерированных LLM. Если вам удастся довести до рабочего состояния любую из этих идей, мне будет интересно узнать о результатах: al@inventwithpython.com.
Паттерны неудач в приложениях от LLM
Обычно LLM не удавалось создать софт со следующими характеристиками:
Немного необычные задачи. Любое приложение, которое не реализовывалось сотни раз (Тетрис, секундомер, список дел и т. п.).
Требующие пространственных или визуальных характеристик. LLM генерируют текст, но работа с координатами или отрисовкой у них быстро разваливается.
Похожие, но не идентичные типовые приложения. Если попросить сделать пинбол — получается пинг-понг. Если нужны аморфные пятна, как в лавовой лампе, LLM рисуют идеальные круги. Модели скатываются к знакомым, но неточным примерам, иногда даже несмотря на прямые указания не делать этого.
Список провалившихся экспериментов с vibe coding:
Викторина по географии стран Африки
Игра «Пинбол»
Генератор круговых лабиринтов
Интерактивные китайские счеты
Симулятор кодового замка
Редактор генеалогических деревьев
Симулятор лавовой лампы
Симулятор снежного шара
Викторина по географии стран Африки
Промпт:
Создай программу на Python с использованием tkinter для GUI и только пакетов из стандартной библиотеки. Приложение показывает простую карту Африки без границ между странами, только контур Африки. Оно выводит название случайной африканской страны, затем пользователь должен кликнуть по карте. Приложение показывает контур этой страны на карте на несколько секунд, после чего выводит название следующей случайной страны. Для простоты в приложении нет очков или баллов. Приложение не сообщает, попал ли пользователь в границы страны, или нет (он и так увидит это, когда появится контур страны). Чтобы упростить, точки остановки нет: викторина продолжается, пока пользователь не закроет окно приложения.
Результат: LLM постоянно «забывают», что Мадагаскар — часть Африки, если им об этом не напоминать. Еще кажется, что LLM считают, будто Африка по форме как картошка.

Я рассчитывал на нечто похожее на эту викторину по географии: показывается Африка без границ стран, а затем пользователю предлагают кликнуть по заданной стране.
Одна из LLM выдала что-то отдаленно похожее на Африку и даже нарисовала Мадагаскар (после того как я напомнил, что он — часть Африки). Но формы стран были... мягко говоря, неточными. Я просил их найти в интернете SVG-файлы карт Африки. Они отвечали, что нашли, а затем снова рисовали картошку.
fail_africa_1.py
import tkinter as tk
from tkinter import ttk
import random
import math
class AfricaGeographyQuiz:
def __init__(self):
self.root = tk.Tk()
self.root.title("African Geography Quiz")
self.root.geometry("800x700")
self.root.configure(bg='lightblue')
# African countries and their approximate center coordinates (normalized 0-1)
self.countries = {
'Algeria': (0.5, 0.25),
'Angola': (0.35, 0.75),
'Benin': (0.45, 0.55),
'Botswana': (0.55, 0.8),
'Burkina Faso': (0.4, 0.5),
'Burundi': (0.65, 0.7),
'Cameroon': (0.5, 0.6),
'Cape Verde': (0.15, 0.45),
'Central African Republic': (0.55, 0.6),
'Chad': (0.55, 0.5),
'Comoros': (0.8, 0.75),
'Democratic Republic of the Congo': (0.55, 0.7),
'Republic of the Congo': (0.5, 0.65),
'Djibouti': (0.8, 0.5),
'Egypt': (0.65, 0.25),
'Equatorial Guinea': (0.48, 0.6),
'Eritrea': (0.75, 0.45),
'Eswatini': (0.65, 0.85),
'Ethiopia': (0.75, 0.55),
'Gabon': (0.48, 0.65),
'Gambia': (0.25, 0.5),
'Ghana': (0.4, 0.55),
'Guinea': (0.3, 0.5),
'Guinea-Bissau': (0.25, 0.5),
'Ivory Coast': (0.35, 0.55),
'Kenya': (0.75, 0.65),
'Lesotho': (0.6, 0.85),
'Liberia': (0.3, 0.55),
'Libya': (0.55, 0.3),
'Madagascar': (0.85, 0.8),
'Malawi': (0.7, 0.75),
'Mali': (0.4, 0.45),
'Mauritania': (0.3, 0.4),
'Mauritius': (0.9, 0.8),
'Morocco': (0.35, 0.2),
'Mozambique': (0.7, 0.8),
'Namibia': (0.5, 0.8),
'Niger': (0.5, 0.45),
'Nigeria': (0.5, 0.55),
'Rwanda': (0.65, 0.7),
'Sao Tome and Principe': (0.45, 0.6),
'Senegal': (0.25, 0.45),
'Seychelles': (0.9, 0.7),
'Sierra Leone': (0.28, 0.55),
'Somalia': (0.85, 0.6),
'South Africa': (0.6, 0.85),
'South Sudan': (0.65, 0.55),
'Sudan': (0.65, 0.45),
'Tanzania': (0.7, 0.7),
'Togo': (0.43, 0.55),
'Tunisia': (0.5, 0.2),
'Uganda': (0.7, 0.65),
'Zambia': (0.6, 0.75),
'Zimbabwe': (0.65, 0.8)
}
self.current_country = None
self.showing_answer = False
self.setup_ui()
self.next_question()
def setup_ui(self):
# Title
title_label = tk.Label(self.root, text="African Geography Quiz",
font=('Arial', 20, 'bold'), bg='lightblue', fg='darkblue')
title_label.pack(pady=10)
# Instructions
instructions = tk.Label(self.root,
text="Click on the map where you think the country is located!",
font=('Arial', 12), bg='lightblue', fg='darkgreen')
instructions.pack(pady=5)
# Country prompt
self.country_label = tk.Label(self.root, text="",
font=('Arial', 16, 'bold'), bg='lightblue', fg='red')
self.country_label.pack(pady=10)
# Canvas for the map
self.canvas = tk.Canvas(self.root, width=600, height=500, bg='lightcyan')
self.canvas.pack(pady=10)
self.canvas.bind("<Button-1>", self.on_map_click)
# Draw Africa outline
self.draw_africa_outline()
def draw_africa_outline(self):
"""Draw a simplified outline of Africa"""
# Simplified Africa outline coordinates (scaled to canvas)
africa_points = [
# Starting from northwest, going clockwise
100, 150, # Morocco area
120, 140, # North Morocco
140, 130, # Algeria
200, 120, # Tunisia
250, 130, # Libya
350, 140, # Egypt
370, 160, # Egypt east
380, 200, # Red Sea
390, 250, # Sudan/Eritrea
400, 300, # Ethiopia
420, 350, # Somalia
430, 400, # Kenya
440, 450, # Tanzania
450, 480, # Mozambique north
460, 500, # Mozambique
450, 520, # South Africa east
400, 530, # South Africa
350, 525, # Lesotho area
300, 520, # South Africa west
250, 510, # Namibia
200, 500, # Angola
150, 480, # Angola north
120, 450, # DRC
100, 400, # Cameroon
90, 350, # Nigeria
85, 300, # Niger
80, 250, # Mali
75, 200, # Mauritania
80, 180, # Morocco west
100, 150 # Back to start
]
self.canvas.create_polygon(africa_points, outline='black', fill='tan', width=2)
def next_question(self):
"""Select a random country for the next question"""
if self.showing_answer:
return
self.current_country = random.choice(list(self.countries.keys()))
self.country_label.config(text=f"Find: {self.current_country}")
# Clear any previous answer markers
self.canvas.delete("answer_marker")
def on_map_click(self, event):
"""Handle clicks on the map"""
if self.showing_answer:
return
# Show where the user clicked
self.canvas.create_oval(event.x-5, event.y-5, event.x+5, event.y+5,
fill='blue', outline='darkblue', width=2, tags="click_marker")
# Show the correct location
self.show_answer()
def show_answer(self):
"""Show the correct location of the country"""
self.showing_answer = True
# Get the country's coordinates and convert to canvas coordinates
country_coords = self.countries[self.current_country]
canvas_x = country_coords[0] * 600 # Scale to canvas width
canvas_y = country_coords[1] * 500 # Scale to canvas height
# Adjust coordinates to fit within Africa outline
canvas_x = max(75, min(525, canvas_x))
canvas_y = max(120, min(480, canvas_y))
# Draw the correct location marker
self.canvas.create_oval(canvas_x-8, canvas_y-8, canvas_x+8, canvas_y+8,
fill='red', outline='darkred', width=3, tags="answer_marker")
# Draw country name near the marker
self.canvas.create_text(canvas_x, canvas_y-20, text=self.current_country,
font=('Arial', 10, 'bold'), fill='darkred', tags="answer_marker")
# Schedule next question
self.root.after(3000, self.prepare_next_question)
def prepare_next_question(self):
"""Prepare for the next question"""
self.showing_answer = False
# Clear click markers
self.canvas.delete("click_marker")
# Answer markers will be cleared in next_question
self.next_question()
def run(self):
"""Start the application"""
self.root.mainloop()
if __name__ == "__main__":
app = AfricaGeographyQuiz()
app.run()
fail_africa_2.py
import tkinter as tk
import random
# --- High-Resolution Coordinate Data ---
# This data provides more detailed outlines for the continent and countries.
# Coordinates are in (longitude, latitude) format.
AFRICA_DATA = {
"Algeria": [[(8.68, 28.97), (8.68, 36.8), (3.28, 36.8), (-1.9, 35.9), (-8.67, 30.9), (-8.67, 21.5), (-4.2, 25.0), (2.4, 23.4), (5.5, 19.3), (11.9, 23.9), (9.5, 28.0), (8.68, 28.97)]],
"Angola": [[(13.2, -4.8), (11.7, -17.9), (24.0, -17.9), (24.0, -10.7), (22.0, -11.2), (21.8, -4.8), (16.5, -5.9), (13.2, -4.8)]],
"Botswana": [[(20.0, -17.7), (20.0, -26.9), (25.3, -25.8), (29.4, -22.1), (26.7, -20.2), (20.0, -17.7)]],
"Cameroon": [[(8.4, 2.2), (16.2, 2.2), (16.2, 13.1), (12.5, 10.5), (10.5, 11.5), (9.3, 9.5), (8.4, 4.5), (8.4, 2.2)]],
"Chad": [[(13.4, 7.4), (24.0, 7.4), (24.0, 19.5), (20.0, 23.4), (15.0, 23.4), (14.3, 12.8), (13.4, 7.4)]],
"Democratic Republic of the Congo": [[(12.2, -5.8), (12.2, -13.4), (29.4, -13.4), (31.2, -8.7), (29.5, -2.5), (30.8, 1.2), (30.0, 4.2), (18.5, 5.4), (16.7, 2.0), (12.2, -5.8)]],
"Egypt": [[(25.0, 22.0), (25.0, 31.6), (34.0, 31.6), (36.9, 27.8), (34.8, 22.0), (25.0, 22.0)]],
"Ethiopia": [[(33.0, 3.4), (44.0, 3.4), (48.0, 8.0), (43.0, 11.0), (42.2, 14.8), (36.3, 14.8), (35.0, 12.5), (33.0, 6.0), (33.0, 3.4)]],
"Kenya": [[(33.9, -4.7), (40.9, -4.7), (41.9, -1.8), (41.0, 4.6), (34.5, 4.6), (34.0, 0.0), (33.9, -4.7)]],
"Libya": [[(9.4, 19.5), (25.0, 19.5), (25.0, 31.6), (11.5, 33.0), (9.4, 29.0), (9.4, 19.5)]],
"Madagascar": [[(43.2, -12.0), (50.5, -15.4), (49.5, -25.6), (43.7, -25.1), (43.2, -12.0)]],
"Mali": [[(-12.2, 10.1), (4.2, 10.1), (4.2, 25.0), (-5.0, 25.0), (-11.5, 14.8), (-12.2, 10.1)]],
"Morocco": [[(-13.1, 27.6), (-8.6, 27.6), (-8.6, 29.0), (-1.0, 32.0), (-1.0, 35.9), (-5.9, 35.9), (-8.8, 33.5), (-13.1, 27.6)]],
"Mozambique": [[(30.2, -26.8), (40.8, -10.4), (35.0, -10.4), (32.5, -16.0), (30.2, -22.0), (30.2, -26.8)]],
"Namibia": [[(11.7, -16.9), (11.7, -28.9), (20.0, -28.9), (20.0, -22.0), (25.2, -17.9), (23.0, -16.9), (11.7, -16.9)]],
"Niger": [[(0.1, 11.9), (16.0, 11.9), (16.0, 20.2), (12.0, 23.5), (5.0, 23.5), (3.0, 16.0), (0.1, 11.9)]],
"Nigeria": [[(2.6, 4.2), (14.6, 4.2), (14.6, 13.8), (4.0, 13.8), (2.6, 6.4), (2.6, 4.2)]],
"Somalia": [[(41.0, -1.7), (51.4, 10.0), (51.0, 12.0), (49.0, 11.8), (41.5, 1.8), (41.0, -1.7)]],
"South Africa": [[(16.4, -28.5), (16.4, -34.8), (28.0, -34.8), (32.9, -26.8), (31.0, -22.1), (22.0, -22.1), (20.0, -24.7), (16.4, -28.5)]],
"Sudan": [[(21.8, 9.7), (38.6, 9.7), (38.6, 18.0), (35.0, 22.0), (25.0, 22.0), (21.8, 12.0), (21.8, 9.7)]],
"Tanzania": [[(29.6, -11.7), (40.4, -4.5), (39.0, -1.0), (30.5, -1.0), (29.6, -8.3), (29.6, -11.7)]],
"Zambia": [[(22.0, -18.0), (33.7, -8.2), (28.7, -8.2), (25.0, -12.0), (22.0, -16.0), (22.0, -18.0)]],
"Zimbabwe": [[(25.2, -22.4), (33.0, -22.4), (33.0, -15.6), (27.0, -15.6), (25.2, -18.0), (25.2, -22.4)]],
}
# Outline of the African continent, including Madagascar
AFRICA_OUTLINE = [
# Mainland
[(-17.5, 14.8), (-16.5, 12.0), (-13.0, 7.5), (-8.0, 4.5), (1.0, 4.5), (8.5, 4.5), (9.5, 1.5), (10.0, -1.0), (12.0, -6.0), (13.0, -10.0), (15.0, -18.0), (18.4, -28.7), (20.0, -34.8), (25.0, -34.8), (32.8, -28.0), (40.8, -10.4), (44.0, -1.0), (51.4, 11.8), (45.0, 15.0), (39.6, 15.8), (34.0, 22.0), (35.8, 30.0), (25.0, 31.5), (10.0, 33.0), (10.2, 37.3), (-5.4, 34.6), (-5.9, 35.9), (-8.8, 33.5), (-13.1, 27.6), (-17.5, 20.9), (-17.5, 14.8)],
# Madagascar
[(43.2, -12.0), (50.5, -15.4), (49.5, -25.6), (43.7, -25.1), (43.2, -12.0)]
]
class AfricaQuizApp:
"""A simple geography quiz application for Africa."""
def __init__(self, root):
"""Initialize the application."""
self.root = root
self.root.title("Africa Geography Quiz")
self.root.configure(bg="#f0f0f0")
# --- Configuration ---
self.canvas_width = 600
self.canvas_height = 650
self.map_padding = 20
self.country_fill_color = "#3498db" # A nice blue
self.africa_outline_color = "#555555"
self.africa_fill_color = "#e0e0e0"
self.reveal_duration_ms = 2000 # 2 seconds
# --- State Variables ---
self.current_country = None
self.country_polygon_ids = [] # Store multiple polygon IDs
self.country_list = list(AFRICA_DATA.keys())
# --- UI Elements ---
self.info_label = tk.Label(
self.root,
text="Find this country:",
font=("Helvetica", 14),
bg="#f0f0f0"
)
self.info_label.pack(pady=(10, 0))
self.country_label = tk.Label(
self.root,
text="",
font=("Helvetica", 20, "bold"),
bg="#f0f0f0",
fg="#2c3e50"
)
self.country_label.pack(pady=(0, 10))
self.canvas = tk.Canvas(
self.root,
width=self.canvas_width,
height=self.canvas_height,
bg="white",
highlightthickness=0
)
self.canvas.pack(padx=20, pady=(0, 20))
self.canvas.bind("<Button-1>", self.on_map_click)
# --- Setup ---
self.calculate_map_bounds()
self.draw_africa()
self.next_country()
def calculate_map_bounds(self):
"""Calculate the bounding box of all coordinates to scale the map."""
all_points = []
for part in AFRICA_OUTLINE:
all_points.extend(part)
for country_polygons in AFRICA_DATA.values():
for polygon in country_polygons:
all_points.extend(polygon)
longitudes = [p[0] for p in all_points]
latitudes = [p[1] for p in all_points]
self.min_lon = min(longitudes)
self.max_lon = max(longitudes)
self.min_lat = min(latitudes)
self.max_lat = max(latitudes)
# Calculate scale and offset to fit map in canvas
lon_range = self.max_lon - self.min_lon
lat_range = self.max_lat - self.min_lat
scale_x = (self.canvas_width - 2 * self.map_padding) / lon_range
scale_y = (self.canvas_height - 2 * self.map_padding) / lat_range
self.scale = min(scale_x, scale_y) * 0.98 # Add a small margin
# Calculate offsets to center the map
map_width = lon_range * self.scale
map_height = lat_range * self.scale
self.x_offset = (self.canvas_width - map_width) / 2
self.y_offset = (self.canvas_height - map_height) / 2
def transform_coords(self, lon, lat):
"""Convert longitude and latitude to canvas coordinates."""
x = (lon - self.min_lon) * self.scale + self.x_offset
# Invert Y because canvas (0,0) is top-left
y = self.canvas_height - ((lat - self.min_lat) * self.scale + self.y_offset)
return x, y
def draw_africa(self):
"""Draw the main outline of Africa on the canvas."""
for part in AFRICA_OUTLINE:
transformed_points = [self.transform_coords(lon, lat) for lon, lat in part]
self.canvas.create_polygon(
transformed_points,
outline=self.africa_outline_color,
fill=self.africa_fill_color,
width=2
)
def next_country(self):
"""Selects and displays the next random country."""
# Clear the previously shown country polygon(s) if they exist
for polygon_id in self.country_polygon_ids:
self.canvas.delete(polygon_id)
self.country_polygon_ids = []
# Pick a new random country
self.current_country = random.choice(self.country_list)
self.country_label.config(text=self.current_country)
def on_map_click(self, event):
"""Handles the user clicking on the map."""
# Prevent multiple clicks while a country is being shown
if self.country_polygon_ids:
return
self.show_country_outline()
# Schedule the next country prompt after a delay
self.root.after(self.reveal_duration_ms, self.next_country)
def show_country_outline(self):
"""Draws the outline of the current country."""
if self.current_country:
country_polygons = AFRICA_DATA[self.current_country]
for polygon in country_polygons:
transformed_points = [self.transform_coords(lon, lat) for lon, lat in polygon]
# Draw each polygon part of the country and store its ID
polygon_id = self.canvas.create_polygon(
transformed_points,
fill=self.country_fill_color,
outline="white",
width=1
)
self.country_polygon_ids.append(polygon_id)
if __name__ == "__main__":
root = tk.Tk()
app = AfricaQuizApp(root)
root.mainloop()
fail_africa_3.py
import tkinter as tk
import random
import time
import threading
# Very simplified Africa outline coordinates (replace with better outline if desired):
AFRICA_OUTLINE = [
(50, 20), (100, 10), (160, 15), (200, 40), (240, 80),
(250, 130), (230, 180), (210, 220), (170, 260), (120, 280),
(80, 300), (60, 260), (50, 200), (40, 150), (40, 80)
]
# Dictionary mapping country names to rough polygon coordinates.
# (These are approximate positions and sizes – refine or expand as needed.)
COUNTRY_COORDS = {
"Egypt": [(160, 40), (200, 45), (200, 75), (160, 70)],
"Nigeria": [(110, 145), (140, 145), (140, 175), (110, 175)],
"South Africa": [(90, 230), (130, 230), (130, 260), (90, 260)],
"Kenya": [(165, 130), (190, 130), (190, 160), (165, 160)],
"Morocco": [(75, 50), (100, 50), (100, 80), (75, 80)],
"Madagascar": [(245, 200), (265, 200), (265, 250), (245, 250)]
}
COUNTRY_LIST = list(COUNTRY_COORDS.keys())
class AfricaQuizApp:
def __init__(self, root):
self.root = root
self.root.title("Africa Geography Quiz")
self.canvas = tk.Canvas(root, width=300, height=320)
self.canvas.pack()
# Draw Africa outline
self.canvas.create_polygon(AFRICA_OUTLINE, outline="black", fill="white")
self.label = tk.Label(root, text="", font=("Arial", 14))
self.label.pack(pady=8)
self.current_country = None
self.country_outline = None
self.canvas.bind("<Button-1>", self.on_click)
self.next_country()
def on_click(self, event):
# Draw country outline regardless of whether click was "correct"
if self.country_outline is not None:
self.canvas.delete(self.country_outline)
coords = COUNTRY_COORDS[self.current_country]
self.country_outline = self.canvas.create_polygon(
coords, outline="red", fill="", width=2
)
# Start a thread so the UI does not freeze
threading.Thread(target=self.show_then_next).start()
def show_then_next(self):
time.sleep(2.0) # show the outline for two seconds
self.canvas.delete(self.country_outline)
self.country_outline = None
self.next_country()
def next_country(self):
self.current_country = random.choice(COUNTRY_LIST)
self.label.config(text=f"Click: {self.current_country}")
if __name__ == "__main__":
root = tk.Tk()
app = AfricaQuizApp(root)
root.mainloop()
Игра «Пинбол»
Промпт:
Создай простую игру в пинбол на Python с использованием tkinter или turtle и только стандартной библиотеки.
Результат: Без исключений LLM предлагали мне вариант пинг-понга с шариком, двигающимся с постоянной скоростью. Пришлось добавлять уточнения: не делать пинг-понг, добавить две лапки-флипперы, реализовать правдоподобную гравитацию и воронкообразные борта, направляющие шар к флипперам. LLM так и не сделали обработку столкновений и оставляли поле пинбола пустым. Я не ожидал «Space Cadet Pinball», но сколько ни переписывай промпт — ничего отдалённо приемлемого не получалось. Я также пробовал примеры на Pygame — на случай, если tkinter слишком ограничивает.
В одном случае левый флиппер был неправильно размещён, но вращался в правильную сторону, а правый — стоял правильно, но вращался в неправильную.

fail_pinball_1.py (использует Pygame)
import pygame
import sys
import math
# Simple pinball game skeleton using Pygame
def main():
pygame.init()
WIDTH, HEIGHT = 600, 800
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
# Ball properties
ball_pos = [WIDTH // 2, HEIGHT // 4]
ball_vel = [0, 0]
gravity = 0.5
radius = 10
# Flipper properties
left_flipper_angle = -30
right_flipper_angle = 30
flipper_length = 80
flipper_speed = 8
def draw_flipper(center, angle):
x1, y1 = center
x2 = x1 + flipper_length * math.cos(math.radians(angle))
y2 = y1 + flipper_length * math.sin(math.radians(angle))
pygame.draw.line(screen, (255, 255, 255), (x1, y1), (x2, y2), 8)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
left_flipper_angle = max(-60, left_flipper_angle - flipper_speed)
else:
left_flipper_angle = min(-30, left_flipper_angle + flipper_speed)
if keys[pygame.K_RIGHT]:
right_flipper_angle = min(60, right_flipper_angle + flipper_speed)
else:
right_flipper_angle = max(30, right_flipper_angle - flipper_speed)
# Update ball physics
ball_vel[1] += gravity
ball_pos[0] += ball_vel[0]
ball_pos[1] += ball_vel[1]
# Funnel walls
if ball_pos[1] > HEIGHT - 200:
if ball_pos[0] < WIDTH // 2:
ball_vel[0] += 0.2
else:
ball_vel[0] -= 0.2
# Boundary collision
if ball_pos[0] < radius or ball_pos[0] > WIDTH - radius:
ball_vel[0] *= -1
if ball_pos[1] < radius:
ball_vel[1] *= -1
screen.fill((0, 0, 0))
# Draw funnel walls
pygame.draw.line(screen, (255, 255, 255), (0, HEIGHT - 200), (WIDTH//2, HEIGHT), 4)
pygame.draw.line(screen, (255, 255, 255), (WIDTH, HEIGHT - 200), (WIDTH//2, HEIGHT), 4)
# Draw flippers
draw_flipper((WIDTH//2 - 100, HEIGHT - 100), left_flipper_angle)
draw_flipper((WIDTH//2 + 100, HEIGHT - 100), 180-right_flipper_angle)
# Draw ball
pygame.draw.circle(screen, (255, 255, 255), (int(ball_pos[0]), int(ball_pos[1])), radius)
pygame.display.flip()
clock.tick(60)
if __name__ == "__main__":
main()
Генератор круговых лабиринтов
Промпт:
Напиши программу на Python, которая использует только tkinter и стандартную библиотеку, чтобы сгенерировать картинку круглого лабиринта. Стены и границы лабиринта не должны быть прямыми линиями или прямоугольниками. Игрок начинает в центре и должен добраться до выхода сверху. Управление стрелками на клавиатуре; игрок не должен проходить сквозь стены.
Результат: LLM без труда генерируют программы для прямоугольных лабиринтов. Но круговые лабиринты получались жалкими подобиями: стены рассыпаны случайно, с недостижимыми участками и очевидными почти прямыми маршрутами. Порой LLM писала программу, которая при всем объёме кода просто показывала пустое окно. А управление с клавиатуры было полностью сломано и неисправимо.

fail_circlemaze_1.py
import tkinter as tk
import math
import random
# --- Configuration ---
CANVAS_SIZE = 800
MAZE_LEVELS = 15 # Number of concentric rings in the maze
BACKGROUND_COLOR = '#F0F0F0'
WALL_COLOR = '#333333'
WALL_WIDTH = 2
SOLUTION_COLOR = '#E74C3C' # Color for the solution path (optional)
class CircularMaze:
"""
A class to generate and hold the data for a circular maze.
The maze is represented as a grid of cells, where each cell is a sector
of a concentric ring.
"""
def __init__(self, levels, sectors_in_first_level=8):
"""
Initializes the circular maze structure.
Args:
levels (int): The number of concentric rings (levels).
sectors_in_first_level (int): The number of cells in the innermost ring.
Each subsequent ring will have more sectors.
"""
self.levels = levels
self.grid = []
self.solution = {}
# --- FIX ---
# The original maze generation had a flaw where the number of sectors in
# one ring was not always an integer multiple of the sectors in the
# previous ring. This broke the neighbor-finding logic and resulted in
# unreachable areas.
#
# This corrected logic ensures that the ratio of sectors between
# adjacent rings is always an integer (1 or 2), allowing the DFS
# algorithm to create a perfect, fully connected maze. We now double
# the number of sectors every few rings.
sectors_list = []
current_sectors = sectors_in_first_level
# This value controls how often the maze complexity increases.
doubling_frequency = 3
for r in range(levels):
# Double the number of sectors every `doubling_frequency` rings.
if r > 0 and r % doubling_frequency == 0:
current_sectors *= 2
sectors_list.append(current_sectors)
# Create the grid using the pre-calculated sector counts.
for sectors in sectors_list:
# Each cell has two walls: 'cw' (clockwise) and 'out' (outward).
# A 'True' value means the wall exists.
self.grid.append([{'cw': True, 'out': True} for _ in range(sectors)])
def get_neighbors(self, r, c):
"""
Finds all valid neighbors for a given cell (r, c).
This handles the complexity of connecting cells between rings of
different sector counts.
"""
neighbors = []
sectors_curr = len(self.grid[r])
# Clockwise neighbor
neighbors.append(((r, (c + 1) % sectors_curr), 'cw'))
# Counter-clockwise neighbor
neighbors.append(((r, (c - 1 + sectors_curr) % sectors_curr), 'ccw'))
# Outward neighbors
if r + 1 < self.levels:
sectors_next = len(self.grid[r+1])
ratio = sectors_next / sectors_curr
for i in range(int(ratio)):
neighbors.append(((r + 1, int(c * ratio) + i), 'out'))
# Inward neighbors
if r > 0:
sectors_prev = len(self.grid[r-1])
ratio = sectors_curr / sectors_prev
neighbors.append(((r - 1, int(c / ratio)), 'in'))
return neighbors
def generate(self):
"""
Generates the maze using a randomized depth-first search (DFS) algorithm.
It carves paths by removing walls between cells.
"""
start_r, start_c = random.randint(0, self.levels - 1), 0
stack = [(start_r, start_c)]
visited = set([(start_r, start_c)])
while stack:
current_r, current_c = stack[-1]
# Find unvisited neighbors
unvisited_neighbors = []
for (nr, nc), direction in self.get_neighbors(current_r, current_c):
if 0 <= nr < self.levels and 0 <= nc < len(self.grid[nr]):
if (nr, nc) not in visited:
unvisited_neighbors.append(((nr, nc), direction))
if unvisited_neighbors:
# Choose a random unvisited neighbor
(next_r, next_c), direction = random.choice(unvisited_neighbors)
# Remove the wall between the current cell and the chosen neighbor
if direction == 'cw':
self.grid[current_r][current_c]['cw'] = False
elif direction == 'ccw':
self.grid[next_r][next_c]['cw'] = False
elif direction == 'out':
self.grid[current_r][current_c]['out'] = False
elif direction == 'in':
self.grid[next_r][next_c]['out'] = False
self.solution[(next_r, next_c)] = (current_r, current_c)
visited.add((next_r, next_c))
stack.append((next_r, next_c))
else:
# Backtrack if there are no unvisited neighbors
stack.pop()
# Create an entrance and an exit
self.grid[0][0]['cw'] = False # Entrance at the center
self.grid[self.levels-1][0]['out'] = False # Exit at the outer edge
def draw(self, canvas):
"""
Draws the entire maze on the provided tkinter canvas.
"""
canvas.delete("all")
width = int(canvas.cget("width"))
height = int(canvas.cget("height"))
center_x, center_y = width / 2, height / 2
# The first ring is a gap, so we start drawing from an offset
ring_thickness = (min(width, height) / 2) / (self.levels + 1)
# Iterate through each cell to draw its walls
for r, level in enumerate(self.grid):
sectors = len(level)
angle_step = 360 / sectors
for c, cell in enumerate(level):
radius_inner = (r + 1) * ring_thickness
radius_outer = (r + 2) * ring_thickness
angle_start = c * angle_step
angle_end = (c + 1) * angle_step
# Draw the outward wall (an arc)
if cell['out']:
canvas.create_arc(
center_x - radius_outer, center_y - radius_outer,
center_x + radius_outer, center_y + radius_outer,
start=angle_start, extent=angle_step,
style=tk.ARC, outline=WALL_COLOR, width=WALL_WIDTH
)
# Draw the clockwise wall (a radial line)
if cell['cw']:
x1 = center_x + radius_inner * math.cos(math.radians(angle_end))
y1 = center_y + radius_inner * math.sin(math.radians(angle_end))
x2 = center_x + radius_outer * math.cos(math.radians(angle_end))
y2 = center_y + radius_outer * math.sin(math.radians(angle_end))
canvas.create_line(x1, y1, x2, y2, fill=WALL_COLOR, width=WALL_WIDTH)
def generate_and_draw_maze():
"""
Function to be called by the button to generate and draw a new maze.
"""
maze = CircularMaze(MAZE_LEVELS)
maze.generate()
maze.draw(canvas)
# --- Main Application Setup ---
if __name__ == "__main__":
root = tk.Tk()
root.title("Circular Maze Generator")
root.configure(bg=BACKGROUND_COLOR)
# --- UI Frame ---
ui_frame = tk.Frame(root, bg=BACKGROUND_COLOR)
ui_frame.pack(pady=10)
title_label = tk.Label(ui_frame, text="Circular Maze Generator", font=("Helvetica", 16), bg=BACKGROUND_COLOR)
title_label.pack(pady=(0, 10))
generate_button = tk.Button(
ui_frame,
text="Generate New Maze",
font=("Helvetica", 12),
command=generate_and_draw_maze
)
generate_button.pack()
# --- Canvas for Maze ---
canvas_frame = tk.Frame(root)
canvas_frame.pack(expand=True, fill=tk.BOTH, padx=20, pady=20)
canvas = tk.Canvas(
canvas_frame,
width=CANVAS_SIZE,
height=CANVAS_SIZE,
bg='white',
highlightthickness=0
)
canvas.pack(expand=True)
# Generate the first maze on startup
generate_and_draw_maze()
root.mainloop()
fail_circlemaze_2.py
import tkinter as tk
import math
import random
# --- Configuration ---
CANVAS_SIZE = 800
MAZE_LEVELS = 15 # Number of concentric rings in the maze
BACKGROUND_COLOR = '#F0F0F0'
WALL_COLOR = '#333333'
WALL_WIDTH = 2
SOLUTION_COLOR = '#E74C3C' # Color for the solution path (optional)
class CircularMaze:
"""
A class to generate and hold the data for a circular maze.
The maze is represented as a grid of cells, where each cell is a sector
of a concentric ring.
"""
def __init__(self, levels, sectors_in_first_level=8):
"""
Initializes the circular maze structure.
Args:
levels (int): The number of concentric rings (levels).
sectors_in_first_level (int): The number of cells in the innermost ring.
Each subsequent ring will have more sectors.
"""
self.levels = levels
self.grid = []
self.solution = {}
# Create a grid where outer rings have more cells to keep cell sizes more uniform.
# The number of sectors in a ring is proportional to its radius.
for r in range(levels):
sectors = sectors_in_first_level * (r + 1)
# Each cell has two walls: 'cw' (clockwise) and 'out' (outward).
# A 'True' value means the wall exists.
self.grid.append([{'cw': True, 'out': True} for _ in range(sectors)])
def get_neighbors(self, r, c):
"""
Finds all valid neighbors for a given cell (r, c).
This handles the complexity of connecting cells between rings of
different sector counts.
"""
neighbors = []
sectors_curr = len(self.grid[r])
# Clockwise neighbor
neighbors.append(((r, (c + 1) % sectors_curr), 'cw'))
# Counter-clockwise neighbor
neighbors.append(((r, (c - 1 + sectors_curr) % sectors_curr), 'ccw'))
# Outward neighbors
if r + 1 < self.levels:
sectors_next = len(self.grid[r+1])
ratio = sectors_next / sectors_curr
for i in range(int(ratio)):
neighbors.append(((r + 1, int(c * ratio) + i), 'out'))
# Inward neighbors
if r > 0:
sectors_prev = len(self.grid[r-1])
ratio = sectors_curr / sectors_prev
neighbors.append(((r - 1, int(c / ratio)), 'in'))
return neighbors
def generate(self):
"""
Generates the maze using a randomized depth-first search (DFS) algorithm.
It carves paths by removing walls between cells.
"""
start_r, start_c = random.randint(0, self.levels - 1), 0
stack = [(start_r, start_c)]
visited = set([(start_r, start_c)])
while stack:
current_r, current_c = stack[-1]
# Find unvisited neighbors
unvisited_neighbors = []
for (nr, nc), direction in self.get_neighbors(current_r, current_c):
if 0 <= nr < self.levels and 0 <= nc < len(self.grid[nr]):
if (nr, nc) not in visited:
unvisited_neighbors.append(((nr, nc), direction))
if unvisited_neighbors:
# Choose a random unvisited neighbor
(next_r, next_c), direction = random.choice(unvisited_neighbors)
# Remove the wall between the current cell and the chosen neighbor
if direction == 'cw':
self.grid[current_r][current_c]['cw'] = False
elif direction == 'ccw':
self.grid[next_r][next_c]['cw'] = False
elif direction == 'out':
self.grid[current_r][current_c]['out'] = False
elif direction == 'in':
self.grid[next_r][next_c]['out'] = False
self.solution[(next_r, next_c)] = (current_r, current_c)
visited.add((next_r, next_c))
stack.append((next_r, next_c))
else:
# Backtrack if there are no unvisited neighbors
stack.pop()
# Create an entrance and an exit
self.grid[0][0]['cw'] = False # Entrance at the center
self.grid[self.levels-1][0]['out'] = False # Exit at the outer edge
def draw(self, canvas):
"""
Draws the entire maze on the provided tkinter canvas.
"""
canvas.delete("all")
width = int(canvas.cget("width"))
height = int(canvas.cget("height"))
center_x, center_y = width / 2, height / 2
# The first ring is a gap, so we start drawing from an offset
ring_thickness = (min(width, height) / 2) / (self.levels + 1)
# Iterate through each cell to draw its walls
for r, level in enumerate(self.grid):
sectors = len(level)
angle_step = 360 / sectors
for c, cell in enumerate(level):
radius_inner = (r + 1) * ring_thickness
radius_outer = (r + 2) * ring_thickness
angle_start = c * angle_step
angle_end = (c + 1) * angle_step
# Draw the outward wall (an arc)
if cell['out']:
canvas.create_arc(
center_x - radius_outer, center_y - radius_outer,
center_x + radius_outer, center_y + radius_outer,
start=angle_start, extent=angle_step,
style=tk.ARC, outline=WALL_COLOR, width=WALL_WIDTH
)
# Draw the clockwise wall (a radial line)
if cell['cw']:
x1 = center_x + radius_inner * math.cos(math.radians(angle_end))
y1 = center_y + radius_inner * math.sin(math.radians(angle_end))
x2 = center_x + radius_outer * math.cos(math.radians(angle_end))
y2 = center_y + radius_outer * math.sin(math.radians(angle_end))
canvas.create_line(x1, y1, x2, y2, fill=WALL_COLOR, width=WALL_WIDTH)
def generate_and_draw_maze():
"""
Function to be called by the button to generate and draw a new maze.
"""
maze = CircularMaze(MAZE_LEVELS)
maze.generate()
maze.draw(canvas)
# --- Main Application Setup ---
if __name__ == "__main__":
root = tk.Tk()
root.title("Circular Maze Generator")
root.configure(bg=BACKGROUND_COLOR)
# --- UI Frame ---
ui_frame = tk.Frame(root, bg=BACKGROUND_COLOR)
ui_frame.pack(pady=10)
title_label = tk.Label(ui_frame, text="Circular Maze Generator", font=("Helvetica", 16), bg=BACKGROUND_COLOR)
title_label.pack(pady=(0, 10))
generate_button = tk.Button(
ui_frame,
text="Generate New Maze",
font=("Helvetica", 12),
command=generate_and_draw_maze
)
generate_button.pack()
# --- Canvas for Maze ---
canvas_frame = tk.Frame(root)
canvas_frame.pack(expand=True, fill=tk.BOTH, padx=20, pady=20)
canvas = tk.Canvas(
canvas_frame,
width=CANVAS_SIZE,
height=CANVAS_SIZE,
bg='white',
highlightthickness=0
)
canvas.pack(expand=True)
# Generate the first maze on startup
generate_and_draw_maze()
root.mainloop()
fail_circlemaze_3.py
import tkinter as tk
import math
import random
# Dimensions of the polar maze.
NUM_RINGS = 10 # Number of concentric circles (increasing this creates a deeper maze)
NUM_SECTORS = 24 # Number of angular divisions (must be >= 4 for a meaningful maze)
CANVAS_SIZE = 600 # Width/Height of the drawing canvas in pixels
MARGIN = 20 # Padding around the circular maze
# Each cell is identified by (ring, sector)
# We'll store whether each of its four "walls" exist:
# 'in' -> wall between this ring and the ring below (toward center)
# 'out' -> wall between this ring and the ring above (toward outer edge)
# 'left' / 'right' -> angular boundaries
def generate_polar_maze(rings, sectors):
# Initialize all walls as present and mark cells as unvisited.
maze = {}
for r in range(rings):
for s in range(sectors):
maze[(r, s)] = {
"visited": False,
"in": True,
"out": True,
"left": True,
"right": True
}
def neighbors(cell):
r, s = cell
nbs = []
# inward neighbor
if r > 0:
nbs.append(((r - 1), s, "in"))
# outward neighbor
if r < rings - 1:
nbs.append(((r + 1), s, "out"))
# left/clockwise neighbor
nbs.append((r, (s - 1) % sectors, "left"))
# right / counterclockwise neighbor
nbs.append((r, (s + 1) % sectors, "right"))
return nbs
# Carve maze using DFS
stack = [(0, 0)]
maze[(0, 0)]["visited"] = True
while stack:
cell = stack[-1]
unvisited = []
for nb_r, nb_s, direction in neighbors(cell):
if not maze[(nb_r, nb_s)]["visited"]:
unvisited.append((nb_r, nb_s, direction))
if not unvisited:
stack.pop()
else:
nb_r, nb_s, direction = random.choice(unvisited)
# Knock down wall in the current cell and corresponding wall in neighbor
if direction == "in":
maze[(cell[0], cell[1])]["in"] = False
maze[(nb_r, nb_s)]["out"] = False
elif direction == "out":
maze[(cell[0], cell[1])]["out"] = False
maze[(nb_r, nb_s)]["in"] = False
elif direction == "left":
maze[(cell[0], cell[1])]["left"] = False
maze[(nb_r, nb_s)]["right"] = False
else: # right
maze[(cell[0], cell[1])]["right"] = False
maze[(nb_r, nb_s)]["left"] = False
maze[(nb_r, nb_s)]["visited"] = True
stack.append((nb_r, nb_s))
return maze
def draw_polar_maze(canvas, maze, rings, sectors):
center = CANVAS_SIZE // 2
radius_step = (CANVAS_SIZE // 2 - MARGIN) / rings
for (r, s), walls in maze.items():
r_inner = r * radius_step
r_outer = (r + 1) * radius_step
start_angle = (360 / sectors) * s
end_angle = start_angle + (360 / sectors)
# Inward wall
if walls["in"]:
canvas.create_arc(
center - r_inner,
center - r_inner,
center + r_inner,
center + r_inner,
start=start_angle,
extent=(360 / sectors),
style=tk.ARC,
width=2
)
# Outward wall
if walls["out"]:
canvas.create_arc(
center - r_outer,
center - r_outer,
center + r_outer,
center + r_outer,
start=start_angle,
extent=(360 / sectors),
style=tk.ARC,
width=2
)
# Left (clockwise) wall
if walls["left"]:
angle_rad = math.radians(start_angle)
x0 = center + r_inner * math.cos(angle_rad)
y0 = center - r_inner * math.sin(angle_rad)
x1 = center + r_outer * math.cos(angle_rad)
y1 = center - r_outer * math.sin(angle_rad)
canvas.create_line(x0, y0, x1, y1, width=2)
# Right (counterclockwise) wall
if walls["right"]:
angle_rad = math.radians(end_angle)
x0 = center + r_inner * math.cos(angle_rad)
y0 = center - r_inner * math.sin(angle_rad)
x1 = center + r_outer * math.cos(angle_rad)
y1 = center - r_outer * math.sin(angle_rad)
canvas.create_line(x0, y0, x1, y1, width=2)
def main():
root = tk.Tk()
root.title("Circular Maze")
canvas = tk.Canvas(root, width=CANVAS_SIZE, height=CANVAS_SIZE, bg="white")
canvas.pack()
maze = generate_polar_maze(NUM_RINGS, NUM_SECTORS)
draw_polar_maze(canvas, maze, NUM_RINGS, NUM_SECTORS)
root.mainloop()
if __name__ == "__main__":
main()
fail_circlemaze_4.py
import tkinter as tk
import math
import random
# --- Configuration Constants ---
CANVAS_SIZE = 800
RINGS = 15 # Number of concentric rings in the maze
WALL_COLOR = "#1a237e"
PATH_COLOR = "#e8eaf6"
PLAYER_COLOR = "#ff5252"
WIN_COLOR = "#4caf50"
WALL_THICKNESS = 2
class CircularMaze:
"""
This class handles the generation and drawing of the circular maze structure.
It uses a recursive backtracking algorithm adapted for a polar grid.
"""
def __init__(self, rings):
self.rings = rings
self.grid = []
self._setup_grid()
self._generate_maze()
def _setup_grid(self):
"""Initializes the grid data structure for the maze."""
# The center is a single cell, ring 0
self.grid.append([{'N': True, 'E': True, 'S': True, 'W': True, 'visited': False}])
for r in range(1, self.rings + 1):
# The number of cells in a ring increases as it gets larger
num_cells = self._get_cells_in_ring(r)
ring_cells = [{'N': True, 'E': True, 'S': True, 'W': True, 'visited': False} for _ in range(num_cells)]
self.grid.append(ring_cells)
def _get_cells_in_ring(self, r):
"""Calculates the number of cells for a given ring."""
# A simple formula to increase cells in outer rings
return int(r * 4 * 1.5) if r > 0 else 1
def _generate_maze(self):
"""
Generates the maze paths using a randomized depth-first search
(recursive backtracking) algorithm.
"""
stack = [(0, 0)] # Start at the center cell (ring 0, cell 0)
self.grid[0][0]['visited'] = True
while stack:
r, c = stack[-1]
neighbors = self._get_unvisited_neighbors(r, c)
if neighbors:
nr, nc, direction = random.choice(neighbors)
# Carve a path between the current cell and the neighbor
self._remove_wall((r, c), (nr, nc), direction)
self.grid[nr][nc]['visited'] = True
stack.append((nr, nc))
else:
# Backtrack
stack.pop()
# Create an exit at the top of the outermost ring
exit_cell_index = len(self.grid[self.rings]) // 4
self.grid[self.rings][exit_cell_index]['N'] = False
def _get_unvisited_neighbors(self, r, c):
"""Finds all valid, unvisited neighbors for a given cell."""
neighbors = []
num_cells_current = self._get_cells_in_ring(r)
# Clockwise neighbor (East)
east_c = (c + 1) % num_cells_current
if not self.grid[r][east_c]['visited']:
neighbors.append((r, east_c, 'E'))
# Counter-clockwise neighbor (West)
west_c = (c - 1 + num_cells_current) % num_cells_current
if not self.grid[r][west_c]['visited']:
neighbors.append((r, west_c, 'W'))
# Outward neighbor (South)
if r + 1 <= self.rings:
num_cells_outer = self._get_cells_in_ring(r + 1)
# Find the corresponding cell in the outer ring
ratio = num_cells_outer / num_cells_current
for i in range(math.floor(ratio * c), math.floor(ratio * (c + 1))):
if not self.grid[r + 1][i]['visited']:
neighbors.append((r + 1, i, 'S'))
# Inward neighbor (North)
if r - 1 >= 0:
num_cells_inner = self._get_cells_in_ring(r - 1)
# Find the corresponding cell in the inner ring
ratio = num_cells_current / num_cells_inner
north_c = int(c // ratio)
if not self.grid[r - 1][north_c]['visited']:
neighbors.append((r - 1, north_c, 'N'))
return neighbors
def _remove_wall(self, cell1, cell2, direction):
"""Removes the wall between two adjacent cells."""
r1, c1 = cell1
r2, c2 = cell2
if direction == 'E': # Moving Clockwise
self.grid[r1][c1]['E'] = False
self.grid[r2][c2]['W'] = False
elif direction == 'W': # Moving Counter-Clockwise
self.grid[r1][c1]['W'] = False
self.grid[r2][c2]['E'] = False
elif direction == 'S': # Moving Outward
self.grid[r1][c1]['S'] = False
self.grid[r2][c2]['N'] = False
elif direction == 'N': # Moving Inward
self.grid[r1][c1]['N'] = False
self.grid[r2][c2]['S'] = False
def draw(self, canvas):
"""Draws the entire maze on the tkinter canvas."""
canvas.delete("all")
canvas.create_rectangle(0, 0, CANVAS_SIZE, CANVAS_SIZE, fill=PATH_COLOR, outline="")
center_x, center_y = CANVAS_SIZE / 2, CANVAS_SIZE / 2
ring_width = (CANVAS_SIZE / 2) / (self.rings + 1)
# Draw walls for each cell
for r in range(1, self.rings + 1):
num_cells = self._get_cells_in_ring(r)
radius_inner = r * ring_width
radius_outer = (r + 1) * ring_width
for c in range(num_cells):
angle_start = (c / num_cells) * 360
angle_extent = (1 / num_cells) * 360
# Draw clockwise wall (radial line)
if self.grid[r][c]['E']:
x1 = center_x + radius_inner * math.cos(math.radians(angle_start + angle_extent))
y1 = center_y + radius_inner * math.sin(math.radians(angle_start + angle_extent))
x2 = center_x + radius_outer * math.cos(math.radians(angle_start + angle_extent))
y2 = center_y + radius_outer * math.sin(math.radians(angle_start + angle_extent))
canvas.create_line(x1, y1, x2, y2, fill=WALL_COLOR, width=WALL_THICKNESS)
# Draw outward wall (arc)
if self.grid[r][c]['S']:
# Tkinter's arc bounding box is defined by top-left and bottom-right corners
x0 = center_x - radius_outer
y0 = center_y - radius_outer
x1 = center_x + radius_outer
y1 = center_y + radius_outer
canvas.create_arc(x0, y0, x1, y1, start=angle_start, extent=angle_extent,
style=tk.ARC, outline=WALL_COLOR, width=WALL_THICKNESS)
# Draw the outermost boundary
final_radius = (self.rings + 1) * ring_width
x0 = center_x - final_radius
y0 = center_y - final_radius
x1 = center_x + final_radius
y1 = center_y + final_radius
exit_cell_index = len(self.grid[self.rings]) // 4
num_cells_outer = self._get_cells_in_ring(self.rings)
angle_start = (exit_cell_index / num_cells_outer) * 360
angle_extent = (1 / num_cells_outer) * 360
# Draw the boundary in two parts to leave an opening for the exit
canvas.create_arc(x0, y0, x1, y1, start=angle_start + angle_extent, extent=360-angle_extent,
style=tk.ARC, outline=WALL_COLOR, width=WALL_THICKNESS * 2)
class Player:
"""Represents the player, handling movement, drawing, and win conditions."""
def __init__(self, canvas, maze):
self.canvas = canvas
self.maze = maze
self.r = 0 # Start at ring 0
self.c = 0 # Start at cell 0
self.player_obj = None
self.has_won = False
self.draw()
def get_cell_center(self):
"""Calculates the pixel coordinates for the center of the player's current cell."""
center_x, center_y = CANVAS_SIZE / 2, CANVAS_SIZE / 2
ring_width = (CANVAS_SIZE / 2) / (self.maze.rings + 1)
if self.r == 0:
return center_x, center_y
num_cells = self.maze._get_cells_in_ring(self.r)
# Angle to the middle of the cell
angle = ((self.c + 0.5) / num_cells) * 2 * math.pi
# Radius to the middle of the ring
radius = (self.r + 0.5) * ring_width
x = center_x + radius * math.cos(angle)
y = center_y + radius * math.sin(angle)
return x, y
def draw(self):
"""Draws or moves the player's icon on the canvas."""
if self.player_obj:
self.canvas.delete(self.player_obj)
x, y = self.get_cell_center()
ring_width = (CANVAS_SIZE / 2) / (self.maze.rings + 1)
# Scale player size with the ring width
player_radius = ring_width / 4
self.player_obj = self.canvas.create_oval(
x - player_radius, y - player_radius,
x + player_radius, y + player_radius,
fill=PLAYER_COLOR, outline=""
)
def move(self, event):
"""Handles key press events for player movement."""
if self.has_won:
return
direction = event.keysym
current_cell = self.maze.grid[self.r][self.c]
moved = False
# --- Movement Logic ---
if direction == "Up": # Move Inward
if not current_cell['N']:
num_cells_current = self.maze._get_cells_in_ring(self.r)
num_cells_inner = self.maze._get_cells_in_ring(self.r - 1)
ratio = num_cells_current / num_cells_inner
self.r -= 1
self.c = int(self.c // ratio)
moved = True
elif direction == "Down": # Move Outward
if not current_cell['S']:
num_cells_current = self.maze._get_cells_in_ring(self.r)
num_cells_outer = self.maze._get_cells_in_ring(self.r + 1)
ratio = num_cells_outer / num_cells_current
# This is a simplification; find a cell that shares a border
# For this generation, any cell within the ratio range is a valid path
self.r += 1
self.c = int(self.c * ratio) + random.randint(0, int(ratio-1))
moved = True
elif direction == "Left": # Move Counter-Clockwise
if not current_cell['W']:
num_cells = self.maze._get_cells_in_ring(self.r)
self.c = (self.c - 1 + num_cells) % num_cells
moved = True
elif direction == "Right": # Move Clockwise
if not current_cell['E']:
num_cells = self.maze._get_cells_in_ring(self.r)
self.c = (self.c + 1) % num_cells
moved = True
if moved:
self.draw()
self.check_win()
def check_win(self):
"""Checks if the player has reached the exit and displays a win message."""
exit_cell_index = len(self.maze.grid[self.maze.rings]) // 4
if self.r == self.maze.rings and self.c == exit_cell_index:
self.has_won = True
self.canvas.create_text(
CANVAS_SIZE / 2, CANVAS_SIZE / 2,
text="You Win!",
font=("Helvetica", 60, "bold"),
fill=WIN_COLOR
)
def main():
"""Main function to set up the game window and start the application."""
root = tk.Tk()
root.title("Circular Maze")
canvas = tk.Canvas(root, width=CANVAS_SIZE, height=CANVAS_SIZE, bg=PATH_COLOR)
canvas.pack()
# Generate and draw the maze
maze = CircularMaze(RINGS)
maze.draw(canvas)
# Create the player
player = Player(canvas, maze)
# Bind arrow keys to player movement
root.bind("<KeyPress>", player.move)
# Center the window
root.update_idletasks()
width = root.winfo_width()
height = root.winfo_height()
x = (root.winfo_screenwidth() // 2) - (width // 2)
y = (root.winfo_screenheight() // 2) - (height // 2)
root.geometry('{}x{}+{}+{}'.format(width, height, x, y))
root.mainloop()
if __name__ == "__main__":
main()
Интерактивные китайские счёты
Промпт:
Создай программу на Python, которая использует только tkinter и стандартную библиотеку, чтобы сделать интерактивные счёты. Пользователь может кликать по косточкам, чтобы сдвигать их. Должны быть ряды “небесных” и “земных” косточек. Число, которое представляют счёты в текущей конфигурации, должно отображаться внизу окна.
Результат: Программы от LLM рисовали счёты, но поведение сдвига косточек всякий раз оказывалось сломанным. При клике сдвигались не те косточки, а вернуться в исходное положение они не могли. Отображаемое число полностью расходилось с реальным и иногда даже становилось отрицательным.

fail_abacus_1.py
import tkinter as tk
from tkinter import ttk
import math
class Abacus:
def __init__(self, root):
self.root = root
self.root.title("Interactive Abacus")
self.root.geometry("800x600")
self.root.configure(bg='#8B4513')
# Abacus configuration
self.num_columns = 10 # Number of digit columns
self.heaven_beads_per_column = 2 # Upper beads (worth 5 each)
self.earth_beads_per_column = 5 # Lower beads (worth 1 each)
# Visual settings
self.bead_radius = 15
self.column_width = 70
self.heaven_section_height = 120
self.earth_section_height = 180
self.crossbar_height = 10
# Data structure to track bead positions
# True = bead is "active" (counting), False = "inactive"
self.heaven_beads = [[False] * self.heaven_beads_per_column for _ in range(self.num_columns)]
self.earth_beads = [[False] * self.earth_beads_per_column for _ in range(self.num_columns)]
self.setup_ui()
self.draw_abacus()
self.update_display()
def setup_ui(self):
# Main frame
self.main_frame = tk.Frame(self.root, bg='#8B4513')
self.main_frame.pack(expand=True, fill='both', padx=20, pady=20)
# Canvas for abacus
canvas_width = self.num_columns * self.column_width + 40
canvas_height = self.heaven_section_height + self.earth_section_height + self.crossbar_height + 60
self.canvas = tk.Canvas(
self.main_frame,
width=canvas_width,
height=canvas_height,
bg='#D2691E',
highlightthickness=2,
highlightbackground='#654321'
)
self.canvas.pack(pady=10)
# Value display
self.value_label = tk.Label(
self.main_frame,
text="Value: 0",
font=('Arial', 18, 'bold'),
bg='#8B4513',
fg='white'
)
self.value_label.pack(pady=10)
# Reset button
self.reset_button = tk.Button(
self.main_frame,
text="Reset Abacus",
command=self.reset_abacus,
font=('Arial', 12),
bg='#CD853F',
fg='black'
)
self.reset_button.pack(pady=5)
# Instructions
instructions = tk.Label(
self.main_frame,
text="Click on beads to slide them. Heaven beads (top) = 5, Earth beads (bottom) = 1",
font=('Arial', 10),
bg='#8B4513',
fg='lightgray',
wraplength=600
)
instructions.pack(pady=5)
def draw_abacus(self):
self.canvas.delete("all")
# Draw frame
frame_margin = 20
frame_width = self.num_columns * self.column_width
frame_height = self.heaven_section_height + self.earth_section_height + self.crossbar_height
self.canvas.create_rectangle(
frame_margin, frame_margin,
frame_margin + frame_width, frame_margin + frame_height,
fill='#8B4513', outline='#654321', width=3
)
# Draw crossbar (separates heaven and earth beads)
crossbar_y = frame_margin + self.heaven_section_height
self.canvas.create_rectangle(
frame_margin, crossbar_y,
frame_margin + frame_width, crossbar_y + self.crossbar_height,
fill='#654321', outline='#654321'
)
# Draw vertical rods and beads for each column
for col in range(self.num_columns):
self.draw_column(col, frame_margin)
# Draw column labels (place values)
for col in range(self.num_columns):
x = frame_margin + col * self.column_width + self.column_width // 2
place_value = 10 ** (self.num_columns - 1 - col)
if place_value >= 1000000:
label = f"{place_value // 1000000}M"
elif place_value >= 1000:
label = f"{place_value // 1000}K"
else:
label = str(place_value)
self.canvas.create_text(
x, frame_margin - 10,
text=label, font=('Arial', 8), fill='black'
)
def draw_column(self, col, frame_margin):
# Calculate column position
col_x = frame_margin + col * self.column_width + self.column_width // 2
# Draw vertical rod
rod_top = frame_margin + 5
rod_bottom = frame_margin + self.heaven_section_height + self.earth_section_height + self.crossbar_height - 5
self.canvas.create_line(
col_x, rod_top, col_x, rod_bottom,
fill='#2F4F4F', width=4
)
# Draw heaven beads (upper section)
heaven_start_y = frame_margin + 10
for bead_idx in range(self.heaven_beads_per_column):
if self.heaven_beads[col][bead_idx]: # Active (counting)
y = heaven_start_y + self.heaven_section_height - 40 - bead_idx * 35
else: # Inactive
y = heaven_start_y + bead_idx * 35
bead_id = self.draw_bead(col_x, y, 'heaven', col, bead_idx)
# Draw earth beads (lower section)
earth_start_y = frame_margin + self.heaven_section_height + self.crossbar_height + 10
for bead_idx in range(self.earth_beads_per_column):
if self.earth_beads[col][bead_idx]: # Active (counting)
y = earth_start_y + bead_idx * 32
else: # Inactive
y = earth_start_y + self.earth_section_height - 50 - bead_idx * 32
bead_id = self.draw_bead(col_x, y, 'earth', col, bead_idx)
def draw_bead(self, x, y, bead_type, col, bead_idx):
# Choose colors based on type and state
if bead_type == 'heaven':
is_active = self.heaven_beads[col][bead_idx]
color = '#FFD700' if is_active else '#FFA500' # Gold when active, orange when inactive
else: # earth
is_active = self.earth_beads[col][bead_idx]
color = '#32CD32' if is_active else '#90EE90' # Green when active, light green when inactive
# Draw bead
bead_id = self.canvas.create_oval(
x - self.bead_radius, y - self.bead_radius,
x + self.bead_radius, y + self.bead_radius,
fill=color, outline='#654321', width=2,
tags=f"{bead_type}_{col}_{bead_idx}"
)
# Bind click event
self.canvas.tag_bind(bead_id, "<Button-1>",
lambda e, t=bead_type, c=col, b=bead_idx: self.toggle_bead(t, c, b))
return bead_id
def toggle_bead(self, bead_type, col, bead_idx):
"""Toggle a bead between active and inactive states"""
if bead_type == 'heaven':
# For heaven beads, we need to maintain proper order
current_state = self.heaven_beads[col][bead_idx]
if current_state: # Currently active, deactivate this and all above
for i in range(bead_idx, self.heaven_beads_per_column):
self.heaven_beads[col][i] = False
else: # Currently inactive, activate this and all below
for i in range(bead_idx + 1):
self.heaven_beads[col][i] = True
else: # earth beads
# For earth beads, maintain proper order
current_state = self.earth_beads[col][bead_idx]
if current_state: # Currently active, deactivate this and all above
for i in range(bead_idx, self.earth_beads_per_column):
self.earth_beads[col][i] = False
else: # Currently inactive, activate this and all below
for i in range(bead_idx + 1):
self.earth_beads[col][i] = True
# Redraw and update display
self.draw_abacus()
self.update_display()
def calculate_value(self):
"""Calculate the total value represented by the abacus"""
total = 0
for col in range(self.num_columns):
place_value = 10 ** (self.num_columns - 1 - col)
# Count active heaven beads (worth 5 each)
heaven_count = sum(1 for bead in self.heaven_beads[col] if bead)
# Count active earth beads (worth 1 each)
earth_count = sum(1 for bead in self.earth_beads[col] if bead)
# Add to total
digit_value = (heaven_count * 5 + earth_count) * place_value
total += digit_value
return total
def update_display(self):
"""Update the value display"""
value = self.calculate_value()
self.value_label.config(text=f"Value: {value:,}")
def reset_abacus(self):
"""Reset all beads to inactive state"""
for col in range(self.num_columns):
for bead_idx in range(self.heaven_beads_per_column):
self.heaven_beads[col][bead_idx] = False
for bead_idx in range(self.earth_beads_per_column):
self.earth_beads[col][bead_idx] = False
self.draw_abacus()
self.update_display()
def main():
root = tk.Tk()
abacus = Abacus(root)
root.mainloop()
if __name__ == "__main__":
main()
fail_abacus_2.py
import tkinter as tk
# --- Constants ---
# You can adjust these to change the abacus's appearance
NUM_RODS = 13
BEAD_COLOR = "#B22222" # Firebrick
ROD_COLOR = "#8B4513" # SaddleBrown
FRAME_COLOR = "#8B4513"
BAR_COLOR = "#A0522D" # Sienna
BG_COLOR = "#F5DEB3" # Wheat
BEAD_WIDTH = 50
BEAD_HEIGHT = 25
ROD_SPACING = 60
FRAME_THICKNESS = 20
BAR_THICKNESS = 10
# --- Main Application Class ---
class AbacusApp:
"""
An interactive abacus application built with tkinter.
"""
def __init__(self, root):
"""Initializes the abacus application."""
self.root = root
self.root.title("Interactive Abacus")
self.root.resizable(False, False)
# Calculate canvas dimensions based on abacus size
self.canvas_width = (NUM_RODS + 1) * ROD_SPACING
self.canvas_height = 8 * BEAD_HEIGHT + 2 * FRAME_THICKNESS
# --- Data Structure ---
# Stores the state of the abacus.
# For each rod, we store a list: [heaven_bead_active, num_earth_beads_active]
# heaven_bead_active: 1 if down (active), 0 if up (inactive)
# num_earth_beads_active: 0-4, number of earth beads pushed up (active)
self.bead_states = [[0, 0] for _ in range(NUM_RODS)]
# --- UI Elements ---
self.canvas = tk.Canvas(
root,
width=self.canvas_width,
height=self.canvas_height,
bg=BG_COLOR
)
self.canvas.pack(pady=10)
self.value_label = tk.Label(
root,
text="0",
font=("Arial", 24, "bold"),
pady=10
)
self.value_label.pack()
# --- Event Binding ---
self.canvas.bind("<Button-1>", self.on_canvas_click)
# --- Initial Draw ---
self.draw_abacus()
self.update_value()
def draw_abacus(self):
"""Clears and redraws the entire abacus based on the current state."""
self.canvas.delete("all")
self.draw_frame()
for i in range(NUM_RODS):
self.draw_rod(i)
def draw_frame(self):
"""Draws the outer frame and the central reckoning bar."""
# Outer frame
self.canvas.create_rectangle(
FRAME_THICKNESS / 2, FRAME_THICKNESS / 2,
self.canvas_width - FRAME_THICKNESS / 2,
self.canvas_height - FRAME_THICKNESS / 2,
width=FRAME_THICKNESS,
outline=FRAME_COLOR
)
# Reckoning bar (the horizontal bar in the middle)
self.bar_y = self.canvas_height / 2 - 2.5 * BEAD_HEIGHT
self.canvas.create_rectangle(
FRAME_THICKNESS, self.bar_y - BAR_THICKNESS / 2,
self.canvas_width - FRAME_THICKNESS, self.bar_y + BAR_THICKNESS / 2,
fill=BAR_COLOR,
width=0
)
def draw_rod(self, rod_index):
"""Draws a single rod and its beads based on its state."""
rod_x = (rod_index + 1) * ROD_SPACING
# Draw the rod itself
self.canvas.create_line(
rod_x, FRAME_THICKNESS,
rod_x, self.canvas_height - FRAME_THICKNESS,
fill=ROD_COLOR,
width=2
)
# --- Draw Heaven Bead (value of 5) ---
heaven_state = self.bead_states[rod_index][0]
if heaven_state == 0: # Inactive (up)
y_center = self.bar_y - 1.5 * BEAD_HEIGHT
else: # Active (down)
y_center = self.bar_y - 0.5 * BEAD_HEIGHT
self.draw_bead(rod_x, y_center)
# --- Draw Earth Beads (value of 1 each) ---
earth_beads_up = self.bead_states[rod_index][1]
# Draw active beads (up)
for i in range(earth_beads_up):
y_center = self.bar_y + (i + 0.5) * BEAD_HEIGHT
self.draw_bead(rod_x, y_center)
# Draw inactive beads (down)
for i in range(4 - earth_beads_up):
y_center = self.bar_y + (earth_beads_up + i + 1.5) * BEAD_HEIGHT
self.draw_bead(rod_x, y_center)
def draw_bead(self, x, y):
"""Helper function to draw a single bead centered at (x, y)."""
self.canvas.create_oval(
x - BEAD_WIDTH / 2, y - BEAD_HEIGHT / 2,
x + BEAD_WIDTH / 2, y + BEAD_HEIGHT / 2,
fill=BEAD_COLOR,
width=2,
outline="black"
)
def on_canvas_click(self, event):
"""Handles mouse clicks on the canvas to move beads."""
# Determine which rod was clicked
rod_index = int((event.x - ROD_SPACING / 2) / ROD_SPACING)
if not (0 <= rod_index < NUM_RODS):
return # Click was outside the rods
# Check if the click was on the heaven bead (above the bar)
if event.y < self.bar_y:
# Toggle heaven bead state
self.bead_states[rod_index][0] = 1 - self.bead_states[rod_index][0]
# Check if the click was on the earth beads (below the bar)
else:
# Determine which bead was clicked based on y-position
bead_pos = int((event.y - self.bar_y) / BEAD_HEIGHT)
# Find the state of the clicked bead
num_up = self.bead_states[rod_index][1]
if bead_pos <= num_up: # Clicked on an 'up' bead
# Move this bead and all above it down
self.bead_states[rod_index][1] = bead_pos - 1
else: # Clicked on a 'down' bead
# Move this bead and all below it up
self.bead_states[rod_index][1] = bead_pos
# Redraw and update the display
self.draw_abacus()
self.update_value()
def update_value(self):
"""Calculates and displays the number represented by the abacus."""
total_value = 0
for i in range(NUM_RODS):
heaven_val = self.bead_states[i][0] * 5
earth_val = self.bead_states[i][1]
rod_value = heaven_val + earth_val
# Calculate place value (units, tens, hundreds, etc. from right to left)
place_value = 10**(NUM_RODS - 1 - i)
total_value += rod_value * place_value
self.value_label.config(text=f"{total_value:,}")
# --- Main Execution ---
if __name__ == "__main__":
main_window = tk.Tk()
app = AbacusApp(main_window)
main_window.mainloop()
fail_abacus_3.py
import tkinter as tk
# Constants for the abacus layout.
NUM_COLUMNS = 6 # number of rods
BEAD_RADIUS = 15
ROD_SPACING = 75
HEAVEN_Y = 50
EARTH_START_Y = 150
EARTH_SPACING = 40
HEAVEN_COUNT = 1
EARTH_COUNT = 4
class Bead:
def __init__(self, canvas, col, row, is_heaven):
self.canvas = canvas
self.col = col # which rod this bead belongs to
self.row = row # index within its group
self.is_heaven = is_heaven
x = (col + 1) * ROD_SPACING
if is_heaven:
y = HEAVEN_Y + row * 2 * BEAD_RADIUS
else:
y = EARTH_START_Y + row * 2 * BEAD_RADIUS
self.x = x
self.y = y
self.id = canvas.create_oval(
x - BEAD_RADIUS, y - BEAD_RADIUS,
x + BEAD_RADIUS, y + BEAD_RADIUS,
fill="tan", outline="black"
)
canvas.tag_bind(self.id, "<Button-1>", self.toggle)
def toggle(self, event):
"""
Slide a bead up or down depending on which group it belongs to.
"""
if self.is_heaven:
# Heaven bead can only move DOWN (towards the bar) or back UP.
target = HEAVEN_Y + BEAD_RADIUS*2
if self.y < target:
dy = (target - self.y)
else:
dy = - (target - HEAVEN_Y)
else:
# Earth beads move upward toward the bar or back down to their start.
target = EARTH_START_Y - BEAD_RADIUS*2
if self.y > target:
dy = -(self.y - target)
else:
dy = (EARTH_START_Y - self.y)
# Move only this bead
self.canvas.move(self.id, 0, dy)
self.y += dy
update_display()
def create_rods(canvas):
for c in range(NUM_COLUMNS):
x = (c + 1) * ROD_SPACING
canvas.create_line(x, HEAVEN_Y - 50, x, EARTH_START_Y + 200, width=4)
def get_value():
value = 0
for col in range(NUM_COLUMNS):
# Check heaven bead
heaven_bead = beads[(col, 0, True)]
if heaven_bead.y > HEAVEN_Y + BEAD_RADIUS:
value += 5 * (10**(NUM_COLUMNS - 1 - col))
# Count earth beads that have moved up
earth_count = 0
for i in range(EARTH_COUNT):
b = beads[(col, i, False)]
if b.y < EARTH_START_Y + i*2*BEAD_RADIUS:
earth_count += 1
value += earth_count * (10**(NUM_COLUMNS - 1 - col))
return value
def update_display():
val = get_value()
label_var.set(f"Value: {val}")
root = tk.Tk()
root.title("Abacus")
canvas = tk.Canvas(root, width=(NUM_COLUMNS+1)*ROD_SPACING,
height=350, bg="white")
canvas.pack()
# Draw the separating bar
canvas.create_line(0, EARTH_START_Y - 30,
(NUM_COLUMNS+1)*ROD_SPACING,
EARTH_START_Y - 30, width=6)
create_rods(canvas)
# Create beads and store them in a dictionary
beads = {}
for col in range(NUM_COLUMNS):
# heaven beads
for r in range(HEAVEN_COUNT):
bead = Bead(canvas, col, r, True)
beads[(col, r, True)] = bead
# earth beads
for r in range(EARTH_COUNT):
bead = Bead(canvas, col, r, False)
beads[(col, r, False)] = bead
# Value display
label_var = tk.StringVar()
label_var.set("Value: 0")
label = tk.Label(root, textvariable=label_var, font=("Helvetica", 14))
label.pack(pady=5)
root.mainloop()
Симулятор кодового замка
Промпт:
Создай программу на Python, которая использует только tkinter и стандартную библиотеку. Это симулятор кодового замка. Программа показывает замок с диском и позволяет пользователю вращать диск, двигая мышью. Комбинация отображается сверху. Если пользователь правильно ввёл комбинацию, при клике по защёлке она должна открыться. Окно должно показывать инструкции по использованию приложения для открытия замка.
Результат: Хотя программы рисовали примитивный кодовый замок и диск с цифрами, вращение диска работало как попало. Цифры на диске всегда оставались вертикальными при вращении (я и не требовал корректного поворота). Ввести комбинацию было невозможно, и даже при корректном вводе клик по защёлке ничего не делал.

fail_combo_1.py
import tkinter as tk
import math
# Configuration
COMBINATION = (22, 7, 15) # (clockwise, counter-clockwise, clockwise)
class CombinationLockApp:
def __init__(self, root):
self.root = root
self.root.title("Combination Lock")
self.canvas = tk.Canvas(root, width=300, height=350, bg="white")
self.canvas.pack()
# Variables
self.angle = 0.0
self.last_x = None
self.entered = []
self.turn_count = 0
self.is_unlocked = False
# Draw latch (simple rectangle)
self.latch = self.canvas.create_rectangle(120, 20, 180, 60, fill="gray")
self.canvas.tag_bind(self.latch, "<Button-1>", self.try_open_latch)
# Draw dial *first* so that text is layered on top
self.dial_center = (150, 200)
self.dial = self.canvas.create_oval(50, 100, 250, 300, fill="lightgray")
# Combination display
self.combo_text = self.canvas.create_text(
150,
90,
text=f"Combination: {COMBINATION}",
font=("Arial", 12),
fill="black"
)
# Draw tick marks and numbers
self.draw_ticks()
# Bind spin handlers
self.canvas.bind("<ButtonPress-1>", self.start_spin)
self.canvas.bind("<B1-Motion>", self.spin_dial)
def draw_ticks(self):
self.ticks = []
r = 100
for n in range(40): # 0–39
angle = math.radians(n * 9)
x = self.dial_center[0] + r * math.sin(angle)
y = self.dial_center[1] - r * math.cos(angle)
tick = self.canvas.create_text(
x, y, text=str(n), font=("Arial", 8), fill="black"
)
self.ticks.append((tick, n))
def start_spin(self, event):
self.last_x = event.x
def spin_dial(self, event):
if self.is_unlocked:
return
delta = event.x - self.last_x
self.last_x = event.x
self.angle += delta * 0.8
self.update_dial_display(delta)
def update_dial_display(self, delta):
for tick, _ in self.ticks:
self.canvas.delete(tick)
self.ticks.clear()
r = 100
for n in range(40):
deg = n * 9 + self.angle
x = self.dial_center[0] + r * math.sin(math.radians(deg))
y = self.dial_center[1] - r * math.cos(math.radians(deg))
tick = self.canvas.create_text(
x, y, text=str(n), font=("Arial", 8), fill="black"
)
self.ticks.append((tick, n))
current_number = int(((-self.angle) % 360) / 9) % 40
direction = -1 if delta > 0 else 1
if not self.entered or self.entered[-1][1] != direction:
self.turn_count += 1
self.entered.append((current_number, direction))
if self.turn_count > 3:
self.entered = self.entered[-3:]
self.turn_count = 3
def try_open_latch(self, event):
if self.is_unlocked:
return
numbers = [turn[0] for turn in self.entered[-3:]]
if tuple(numbers) == COMBINATION:
self.is_unlocked = True
self.canvas.itemconfig(self.latch, fill="green")
self.canvas.itemconfig(self.combo_text, text="Unlocked!", fill="green")
else:
self.entered.clear()
self.turn_count = 0
self.canvas.itemconfig(self.combo_text, text=f"Combination: {COMBINATION}", fill="black")
if __name__ == "__main__":
root = tk.Tk()
CombinationLockApp(root)
root.mainloop()
fail_combo_2.py
import tkinter as tk
import math
class CombinationLock:
def __init__(self, root):
self.root = root
self.root.title("Combination Lock Simulator")
self.root.geometry("500x600")
self.root.configure(bg='#2c3e50')
# Lock settings
self.combination = [15, 35, 8] # The correct combination
self.current_sequence = []
self.dial_angle = 0 # Current angle of the dial
self.is_dragging = False
self.last_mouse_angle = 0
self.latch_open = False
# Colors
self.bg_color = '#2c3e50'
self.dial_color = '#34495e'
self.number_color = '#ecf0f1'
self.pointer_color = '#e74c3c'
self.latch_color = '#95a5a6'
self.open_latch_color = '#27ae60'
self.setup_ui()
def setup_ui(self):
# Title and combination display
title_frame = tk.Frame(self.root, bg=self.bg_color)
title_frame.pack(pady=20)
tk.Label(title_frame, text="Combination Lock",
font=('Arial', 20, 'bold'),
fg=self.number_color, bg=self.bg_color).pack()
tk.Label(title_frame, text=f"Combination: {'-'.join(map(str, self.combination))}",
font=('Arial', 14),
fg='#f39c12', bg=self.bg_color).pack(pady=5)
# Current sequence display
self.sequence_label = tk.Label(title_frame, text="Entered: []",
font=('Arial', 12),
fg=self.number_color, bg=self.bg_color)
self.sequence_label.pack(pady=5)
# Instructions
instructions = tk.Label(title_frame,
text="Drag the dial to enter numbers, then click the latch to open",
font=('Arial', 10),
fg='#bdc3c7', bg=self.bg_color)
instructions.pack(pady=5)
# Main canvas for the lock
self.canvas = tk.Canvas(self.root, width=400, height=400,
bg=self.bg_color, highlightthickness=0)
self.canvas.pack(pady=20)
# Reset button
reset_btn = tk.Button(self.root, text="Reset", command=self.reset_lock,
font=('Arial', 12), bg='#e67e22', fg='white',
activebackground='#d35400', relief='flat', pady=5)
reset_btn.pack(pady=10)
# Bind mouse events
self.canvas.bind("<Button-1>", self.on_mouse_down)
self.canvas.bind("<B1-Motion>", self.on_mouse_drag)
self.canvas.bind("<ButtonRelease-1>", self.on_mouse_up)
self.draw_lock()
def draw_lock(self):
self.canvas.delete("all")
center_x, center_y = 200, 200
dial_radius = 120
# Draw outer ring of lock
self.canvas.create_oval(center_x - dial_radius - 20, center_y - dial_radius - 20,
center_x + dial_radius + 20, center_y + dial_radius + 20,
fill='#34495e', outline='#2c3e50', width=3)
# Draw main dial
self.canvas.create_oval(center_x - dial_radius, center_y - dial_radius,
center_x + dial_radius, center_y + dial_radius,
fill=self.dial_color, outline='#2c3e50', width=2)
# Draw numbers around the dial
for i in range(40): # 0-39 numbers
angle = math.radians(i * 9 - self.dial_angle) # 9 degrees per number
number_radius = dial_radius - 25
x = center_x + number_radius * math.cos(angle)
y = center_y + number_radius * math.sin(angle)
# Highlight every 5th number
if i % 5 == 0:
font_size = 12
color = '#f39c12'
else:
font_size = 10
color = self.number_color
self.canvas.create_text(x, y, text=str(i), fill=color,
font=('Arial', font_size, 'bold'))
# Draw center hub
self.canvas.create_oval(center_x - 15, center_y - 15,
center_x + 15, center_y + 15,
fill='#2c3e50', outline='#34495e', width=2)
# Draw pointer at top (fixed position)
pointer_y = center_y - dial_radius - 35
self.canvas.create_polygon(center_x, pointer_y,
center_x - 8, pointer_y + 15,
center_x + 8, pointer_y + 15,
fill=self.pointer_color, outline='#c0392b', width=2)
# Draw latch
latch_color = self.open_latch_color if self.latch_open else self.latch_color
latch_y = center_y + dial_radius + 40
if self.latch_open:
# Draw open latch (moved down)
self.canvas.create_rectangle(center_x - 20, latch_y + 10,
center_x + 20, latch_y + 30,
fill=latch_color, outline='#27ae60', width=2,
tags="latch")
self.canvas.create_text(center_x, latch_y + 20, text="OPEN",
fill='white', font=('Arial', 8, 'bold'))
else:
# Draw closed latch
self.canvas.create_rectangle(center_x - 20, latch_y,
center_x + 20, latch_y + 20,
fill=latch_color, outline='#7f8c8d', width=2,
tags="latch")
self.canvas.create_text(center_x, latch_y + 10, text="PUSH",
fill='white', font=('Arial', 8, 'bold'))
def get_mouse_angle(self, event):
center_x, center_y = 200, 200
dx = event.x - center_x
dy = event.y - center_y
return math.degrees(math.atan2(dy, dx))
def on_mouse_down(self, event):
# Check if clicking on latch
if self.canvas.find_closest(event.x, event.y)[0] in self.canvas.find_withtag("latch"):
self.try_open_latch()
return
# Check if clicking on dial area
center_x, center_y = 200, 200
distance = math.sqrt((event.x - center_x)**2 + (event.y - center_y)**2)
if distance <= 120: # Within dial radius
self.is_dragging = True
self.last_mouse_angle = self.get_mouse_angle(event)
def on_mouse_drag(self, event):
if not self.is_dragging:
return
current_angle = self.get_mouse_angle(event)
angle_diff = current_angle - self.last_mouse_angle
# Handle angle wrap-around
if angle_diff > 180:
angle_diff -= 360
elif angle_diff < -180:
angle_diff += 360
self.dial_angle += angle_diff
self.dial_angle = self.dial_angle % 360
self.last_mouse_angle = current_angle
self.draw_lock()
def on_mouse_up(self, event):
if self.is_dragging:
self.is_dragging = False
# Record the number the pointer is pointing to
self.record_current_number()
def record_current_number(self):
# Calculate which number the pointer is currently pointing to
# The pointer is at the top, so we need to find which number is at angle 270 degrees (top)
pointer_angle = 270 # Top position
adjusted_angle = (pointer_angle + self.dial_angle) % 360
# Each number is 9 degrees apart
number = int((adjusted_angle / 9) + 0.5) % 40
self.current_sequence.append(number)
# Keep only the last 3 numbers (length of combination)
if len(self.current_sequence) > len(self.combination):
self.current_sequence = self.current_sequence[-len(self.combination):]
# Update display
self.sequence_label.config(text=f"Entered: {self.current_sequence}")
def try_open_latch(self):
if len(self.current_sequence) == len(self.combination):
if self.current_sequence == self.combination:
self.latch_open = True
self.sequence_label.config(text="🎉 UNLOCKED! 🎉", fg='#27ae60')
else:
self.sequence_label.config(text="⌠Wrong combination!", fg='#e74c3c')
# Reset after wrong attempt
self.root.after(1500, self.reset_sequence)
else:
self.sequence_label.config(text="Enter complete combination first!", fg='#f39c12')
self.draw_lock()
def reset_sequence(self):
self.current_sequence = []
self.sequence_label.config(text="Entered: []", fg=self.number_color)
def reset_lock(self):
self.current_sequence = []
self.dial_angle = 0
self.latch_open = False
self.sequence_label.config(text="Entered: []", fg=self.number_color)
self.draw_lock()
def main():
root = tk.Tk()
app = CombinationLock(root)
root.mainloop()
if __name__ == "__main__":
main()
fail_combo_3.py
import tkinter as tk
import math
# Configuration
COMBINATION = (22, 7, 15) # (clockwise, counter-clockwise, clockwise)
class CombinationLockApp:
def __init__(self, root):
self.root = root
self.root.title("Combination Lock")
self.canvas = tk.Canvas(root, width=300, height=350, bg="white")
self.canvas.pack()
# Variables
self.angle = 0.0
self.last_x = None
self.entered = []
self.turn_count = 0
self.is_unlocked = False
# Draw latch (simple rectangle)
self.latch = self.canvas.create_rectangle(120, 20, 180, 60, fill="gray")
self.canvas.tag_bind(self.latch, "<Button-1>", self.try_open_latch)
# Combination display
self.combo_text = self.canvas.create_text(150, 90, text=f"Combination: {COMBINATION}",
font=("Arial", 12))
# Draw dial
self.dial_center = (150, 200)
self.dial = self.canvas.create_oval(50, 100, 250, 300, fill="lightgray")
self.canvas.bind("<ButtonPress-1>", self.start_spin)
self.canvas.bind("<B1-Motion>", self.spin_dial)
# Draw tick marks and numbers
self.draw_ticks()
def draw_ticks(self):
self.ticks = []
r = 100
for n in range(40): # standard 0-39 dial
angle = math.radians(n * 9) # 360/40 = 9 degrees per number
x = self.dial_center[0] + r*math.sin(angle)
y = self.dial_center[1] - r*math.cos(angle)
tick = self.canvas.create_text(x, y, text=str(n), font=("Arial", 8))
self.ticks.append((tick, n))
def start_spin(self, event):
self.last_x = event.x
def spin_dial(self, event):
if self.is_unlocked: # dial no longer spins
return
delta = event.x - self.last_x
self.last_x = event.x
# Rotate dial
self.angle += delta * 0.8 # speed factor
self.update_dial_display(delta)
def update_dial_display(self, delta):
# Move text numbers.
# First erase old
for tick, _ in self.ticks:
self.canvas.delete(tick)
# Recreate with new angle
self.ticks.clear()
r = 100
for n in range(40):
deg = n * 9 + self.angle
x = self.dial_center[0] + r * math.sin(math.radians(deg))
y = self.dial_center[1] - r * math.cos(math.radians(deg))
tick = self.canvas.create_text(x, y, text=str(n), font=("Arial", 8))
self.ticks.append((tick, n))
# Track combination logic
current_number = int(((-self.angle) % 360) / 9) % 40
# Detect direction changes (left drag = clockwise, right drag = ccw)
direction = -1 if delta > 0 else 1
if not self.entered or self.entered[-1][1] != direction:
self.turn_count += 1
self.entered.append((current_number, direction))
if self.turn_count > 3:
self.entered = self.entered[-3:]
self.turn_count = 3
def try_open_latch(self, event):
if self.is_unlocked:
return
# Extract only the numbers from most recent 3 turns
numbers = [turn[0] for turn in self.entered[-3:]]
if tuple(numbers) == COMBINATION:
self.is_unlocked = True
self.canvas.itemconfig(self.latch, fill="green")
self.canvas.itemconfig(self.combo_text, text="Unlocked!")
else:
# Reset for another try
self.entered.clear()
self.turn_count = 0
self.canvas.itemconfig(self.combo_text, text=f"Combination: {COMBINATION}")
if __name__ == "__main__":
root = tk.Tk()
CombinationLockApp(root)
root.mainloop()
Редактор диаграмм родословной
Промпт:
Создай программу на Python, которая использует только tkinter и стандартную библиотеку. Сделай приложение для построения диаграммы семьи. Программа представляет людей квадратами с подписью-именем. Приложение запускается с одним квадратом. По клику на квадрат можно отредактировать имя, добавить супруга, добавить ребёнка, добавить родителя или удалить человека. Диаграмма должна автоматически перерисовываться по мере добавления или удаления людей. Используй стандартные линии диаграммы родословной.
Результат: Полностью нерабочее решение. Окно отображало исходный квадрат с человеком, и в некоторых сгенерированных приложениях мне удавалось отредактировать имя. Но добавление любых связей либо ничего не делало, либо полностью ломало/скрывало существующую диаграмму. Нарисовать второй квадрат так и не получилось ни разу.

fail_familytree_1.py
import tkinter as tk
from tkinter import simpledialog, messagebox
class Person:
def __init__(self, name="Person"):
self.name = name
self.spouse = None
self.parents = [] # up to 2
self.children = []
class FamilyTreeApp(tk.Tk):
BOX_W = 80
BOX_H = 40
V_GAP = 50
H_GAP = 40
def __init__(self):
super().__init__()
self.title("Family Tree")
self.canvas = tk.Canvas(self, width=1000, height=700, bg="white")
self.canvas.pack(fill="both", expand=True)
self.root_person = Person()
self.box_items = {}
self.canvas.bind("<Button-1>", self.on_click)
self.draw_tree()
def edit_name(self, person):
new_name = simpledialog.askstring("Edit Name", "Enter name:", initialvalue=person.name)
if new_name:
person.name = new_name
self.draw_tree()
def add_spouse(self, person):
if person.spouse:
messagebox.showinfo("Info", "This person already has a spouse.")
return
person.spouse = Person("Spouse")
person.spouse.spouse = person
self.draw_tree()
def add_child(self, person):
child = Person("Child")
child.parents = [person]
if person.spouse:
child.parents.append(person.spouse)
for p in child.parents:
p.children.append(child)
self.draw_tree()
def add_parent(self, person):
if len(person.parents) >= 2:
messagebox.showinfo("Info", "This person already has 2 parents.")
return
parent = Person("Parent")
parent.children.append(person)
person.parents.append(parent)
self.draw_tree()
def delete_person(self, person):
if person.spouse:
person.spouse.spouse = None
for p in person.parents:
if person in p.children:
p.children.remove(person)
for ch in person.children:
if person in ch.parents:
ch.parents.remove(person)
if person is self.root_person:
self.root_person = Person()
self.draw_tree()
def draw_tree(self):
self.canvas.delete("all")
self.box_items.clear()
self.layout_positions = {}
# center the tree in the canvas
self.update_idletasks()
canvas_mid = self.canvas.winfo_width() // 2
self._assign_positions(self.root_person, 0, canvas_mid)
self._draw_connections(self.root_person)
self._draw_boxes(self.root_person)
def _assign_positions(self, person, level, offset):
if person in self.layout_positions:
return self.layout_positions[person][0]
if person.children:
child_xs = []
for ch in person.children:
child_xs.append(self._assign_positions(ch, level + 1, offset))
offset = child_xs[-1] + self.BOX_W + self.H_GAP
center_x = (child_xs[0] + child_xs[-1]) / 2
else:
center_x = offset
offset += self.BOX_W + self.H_GAP
self.layout_positions[person] = (center_x, level)
if person.spouse:
spouse_x = center_x + self.BOX_W + 10
self.layout_positions[person.spouse] = (spouse_x, level)
return center_x
def _draw_connections(self, person, drawn=set()):
if person.spouse and (person, person.spouse) not in drawn:
x, y = self._get_box_center(person)
sx, sy = self._get_box_center(person.spouse)
self.canvas.create_line(x, y, sx, sy)
drawn.add((person, person.spouse))
drawn.add((person.spouse, person))
for ch in person.children:
cx, cy = self._get_box_center(ch)
px, py = self._get_box_center(person)
self.canvas.create_line(px, py + self.BOX_H//2, cx, cy - self.BOX_H//2)
if person.spouse:
sx, sy = self._get_box_center(person.spouse)
self.canvas.create_line(sx, sy + self.BOX_H//2, cx, cy - self.BOX_H//2)
self._draw_connections(ch, drawn)
def _draw_boxes(self, person, drawn=set()):
if person in drawn:
return
drawn.add(person)
x, level = self.layout_positions[person]
y = level * (self.BOX_H + self.V_GAP)
box = self.canvas.create_rectangle(
x, y, x + self.BOX_W, y + self.BOX_H, outline="black", fill="lightyellow"
)
text = self.canvas.create_text(
x + self.BOX_W/2, y + self.BOX_H/2, text=person.name
)
self.box_items[box] = person
self.box_items[text] = person
if person.spouse:
self._draw_boxes(person.spouse, drawn)
for ch in person.children:
self._draw_boxes(ch, drawn)
def _get_box_center(self, person):
x, level = self.layout_positions[person]
y = level * (self.BOX_H + self.V_GAP)
return (x + self.BOX_W/2, y + self.BOX_H/2)
def on_click(self, event):
items = self.canvas.find_closest(event.x, event.y)
if not items:
return
person = self.box_items.get(items[0])
if person:
self.popup_menu(person, event.x_root, event.y_root)
def popup_menu(self, person, x, y):
menu = tk.Menu(self, tearoff=0)
menu.add_command(label="Edit name", command=lambda: self.edit_name(person))
menu.add_command(label="Add spouse", command=lambda: self.add_spouse(person))
menu.add_command(label="Add child", command=lambda: self.add_child(person))
menu.add_command(label="Add parent", command=lambda: self.add_parent(person))
menu.add_command(label="Delete", command=lambda: self.delete_person(person))
menu.tk_popup(x, y)
if __name__ == "__main__":
app = FamilyTreeApp()
app.mainloop()
fail_familytree_2.py
import tkinter as tk
from tkinter import messagebox, simpledialog
import math
class Person:
def __init__(self, name="New Person", x=0, y=0):
self.name = name
self.x = x
self.y = y
self.spouse = None
self.children = []
self.parents = []
self.rect_id = None
self.text_id = None
def add_child(self, child):
if child not in self.children:
self.children.append(child)
child.parents.append(self)
def add_parent(self, parent):
if parent not in self.parents:
self.parents.append(parent)
parent.children.append(self)
def set_spouse(self, spouse):
if self.spouse:
self.spouse.spouse = None
self.spouse = spouse
if spouse:
spouse.spouse = self
def remove_from_family(self):
# Remove spouse relationship
if self.spouse:
self.spouse.spouse = None
# Remove from parents' children lists
for parent in self.parents:
if self in parent.children:
parent.children.remove(self)
# Remove from children's parents lists
for child in self.children:
if self in child.parents:
child.parents.remove(self)
class FamilyTreeApp:
def __init__(self, root):
self.root = root
self.root.title("Family Tree Diagram")
self.root.geometry("1000x700")
# Create canvas with scrollbars
self.create_canvas()
# Data structures
self.people = []
self.selected_person = None
# Layout parameters
self.person_width = 120
self.person_height = 60
self.level_height = 120
self.person_spacing = 150
# Create initial person
initial_person = Person("Click to Edit", 400, 300)
self.people.append(initial_person)
self.draw_tree()
def create_canvas(self):
# Main frame
main_frame = tk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True)
# Canvas with scrollbars
self.canvas = tk.Canvas(main_frame, bg='white', scrollregion=(0, 0, 2000, 2000))
v_scrollbar = tk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.canvas.yview)
h_scrollbar = tk.Scrollbar(main_frame, orient=tk.HORIZONTAL, command=self.canvas.xview)
self.canvas.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set)
v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Bind events
self.canvas.bind("<Button-1>", self.on_click)
self.canvas.bind("<Button-3>", self.on_right_click) # Right click for context menu
def calculate_layout(self):
"""Calculate positions for all people in the tree"""
if not self.people:
return
# Find root people (those with no parents)
roots = [p for p in self.people if not p.parents]
if not roots:
# If no roots found, pick the first person
roots = [self.people[0]]
# Calculate levels
levels = {}
self.assign_levels(roots, levels, 0)
# Group people by level
level_groups = {}
for person, level in levels.items():
if level not in level_groups:
level_groups[level] = []
level_groups[level].append(person)
# Calculate positions
canvas_width = 2000
for level, people_in_level in level_groups.items():
y = 100 + level * self.level_height
# Sort people in level to keep families together
people_in_level.sort(key=lambda p: self.get_family_sort_key(p))
total_width = len(people_in_level) * self.person_spacing
start_x = (canvas_width - total_width) // 2
for i, person in enumerate(people_in_level):
person.x = start_x + i * self.person_spacing
person.y = y
def assign_levels(self, people, levels, level):
"""Recursively assign levels to people"""
for person in people:
if person not in levels:
levels[person] = level
# Process children at next level
if person.children:
self.assign_levels(person.children, levels, level + 1)
def get_family_sort_key(self, person):
"""Generate a sort key to keep families together"""
# Use parent names as primary sort key
if person.parents:
return ''.join(sorted([p.name for p in person.parents]))
return person.name
def draw_tree(self):
"""Draw the entire family tree"""
self.canvas.delete("all")
# Calculate layout
self.calculate_layout()
# Draw connections first (so they appear behind rectangles)
self.draw_connections()
# Draw people
for person in self.people:
self.draw_person(person)
def draw_person(self, person):
"""Draw a single person as a rectangle with text"""
x1 = person.x - self.person_width // 2
y1 = person.y - self.person_height // 2
x2 = person.x + self.person_width // 2
y2 = person.y + self.person_height // 2
# Choose color based on selection
color = "lightblue" if person == self.selected_person else "lightgray"
# Draw rectangle
person.rect_id = self.canvas.create_rectangle(
x1, y1, x2, y2,
fill=color, outline="black", width=2,
tags=f"person_{id(person)}"
)
# Draw text
person.text_id = self.canvas.create_text(
person.x, person.y,
text=person.name,
font=("Arial", 10, "bold"),
width=self.person_width - 10,
tags=f"person_{id(person)}"
)
def draw_connections(self):
"""Draw family relationship lines"""
for person in self.people:
# Draw spouse connection
if person.spouse and id(person) < id(person.spouse): # Draw only once per couple
self.draw_spouse_line(person, person.spouse)
# Draw parent-child connections
if person.children:
self.draw_parent_child_lines(person)
def draw_spouse_line(self, person1, person2):
"""Draw line between spouses"""
self.canvas.create_line(
person1.x, person1.y,
person2.x, person2.y,
fill="red", width=3, tags="connection"
)
def draw_parent_child_lines(self, parent):
"""Draw lines from parent to children"""
if not parent.children:
return
# Find the spouse to determine the family unit center
spouse = parent.spouse
if spouse:
family_x = (parent.x + spouse.x) // 2
family_y = min(parent.y, spouse.y)
else:
family_x = parent.x
family_y = parent.y
# Draw vertical line down from family unit
children_y = min([child.y for child in parent.children])
mid_y = family_y + (children_y - family_y) // 2
self.canvas.create_line(
family_x, family_y + self.person_height // 2,
family_x, mid_y,
fill="blue", width=2, tags="connection"
)
# Draw horizontal line across children
if len(parent.children) > 1:
left_x = min([child.x for child in parent.children])
right_x = max([child.x for child in parent.children])
self.canvas.create_line(
left_x, mid_y,
right_x, mid_y,
fill="blue", width=2, tags="connection"
)
# Draw vertical lines to each child
for child in parent.children:
self.canvas.create_line(
child.x, mid_y,
child.x, child.y - self.person_height // 2,
fill="blue", width=2, tags="connection"
)
def on_click(self, event):
"""Handle left mouse click"""
clicked_item = self.canvas.find_closest(event.x, event.y)[0]
# Find which person was clicked
clicked_person = None
for person in self.people:
if (person.rect_id == clicked_item or person.text_id == clicked_item):
clicked_person = person
break
if clicked_person:
self.selected_person = clicked_person
self.show_edit_menu(clicked_person, event.x, event.y)
else:
self.selected_person = None
self.draw_tree()
def on_right_click(self, event):
"""Handle right mouse click - same as left click for simplicity"""
self.on_click(event)
def show_edit_menu(self, person, x, y):
"""Show context menu for editing person"""
menu = tk.Menu(self.root, tearoff=0)
menu.add_command(label="Edit Name", command=lambda: self.edit_name(person))
menu.add_command(label="Add Spouse", command=lambda: self.add_spouse(person))
menu.add_command(label="Add Child", command=lambda: self.add_child(person))
menu.add_command(label="Add Parent", command=lambda: self.add_parent(person))
menu.add_separator()
menu.add_command(label="Delete Person", command=lambda: self.delete_person(person))
try:
menu.tk_popup(event.x_root, event.y_root)
finally:
menu.grab_release()
def edit_name(self, person):
"""Edit person's name"""
new_name = simpledialog.askstring("Edit Name", "Enter new name:", initialvalue=person.name)
if new_name:
person.name = new_name
self.draw_tree()
def add_spouse(self, person):
"""Add spouse to person"""
if person.spouse:
messagebox.showwarning("Warning", f"{person.name} already has a spouse!")
return
spouse_name = simpledialog.askstring("Add Spouse", "Enter spouse's name:")
if spouse_name:
spouse = Person(spouse_name, person.x + self.person_spacing, person.y)
person.set_spouse(spouse)
self.people.append(spouse)
self.draw_tree()
def add_child(self, person):
"""Add child to person"""
child_name = simpledialog.askstring("Add Child", "Enter child's name:")
if child_name:
child = Person(child_name, person.x, person.y + self.level_height)
person.add_child(child)
# If person has spouse, add child to spouse too
if person.spouse:
person.spouse.add_child(child)
self.people.append(child)
self.draw_tree()
def add_parent(self, person):
"""Add parent to person"""
parent_name = simpledialog.askstring("Add Parent", "Enter parent's name:")
if parent_name:
parent = Person(parent_name, person.x, person.y - self.level_height)
person.add_parent(parent)
self.people.append(parent)
self.draw_tree()
def delete_person(self, person):
"""Delete person from tree"""
if len(self.people) == 1:
messagebox.showwarning("Warning", "Cannot delete the last person!")
return
result = messagebox.askyesno("Confirm Delete",
f"Are you sure you want to delete {person.name}?")
if result:
person.remove_from_family()
self.people.remove(person)
if self.selected_person == person:
self.selected_person = None
self.draw_tree()
def main():
root = tk.Tk()
app = FamilyTreeApp(root)
root.mainloop()
if __name__ == "__main__":
main()
fail_familytree_3.py
import tkinter as tk
from tkinter import simpledialog, messagebox
class Person:
def __init__(self, name="Person"):
self.name = name
self.spouse = None
self.parents = [] # up to 2
self.children = []
class FamilyTreeApp(tk.Tk):
BOX_W = 80
BOX_H = 40
V_GAP = 50
H_GAP = 40
def __init__(self):
super().__init__()
self.title("Family Tree")
self.canvas = tk.Canvas(self, width=1000, height=700, bg="white")
self.canvas.pack(fill="both", expand=True)
self.root_person = Person()
self.box_items = {} # maps canvas item id -> Person
self.canvas.bind("<Button-1>", self.on_click)
self.draw_tree()
# ----- Tree editing actions -----
def edit_name(self, person):
new_name = simpledialog.askstring("Edit Name", "Enter name:", initialvalue=person.name)
if new_name:
person.name = new_name
self.draw_tree()
def add_spouse(self, person):
if person.spouse:
messagebox.showinfo("Info", "This person already has a spouse.")
return
person.spouse = Person("Spouse")
person.spouse.spouse = person
self.draw_tree()
def add_child(self, person):
child = Person("Child")
# Set parents
child.parents = [person]
if person.spouse: # If married, both become parents
child.parents.append(person.spouse)
# Add child to each parent
for p in child.parents:
p.children.append(child)
self.draw_tree()
def add_parent(self, person):
if len(person.parents) >= 2:
messagebox.showinfo("Info", "This person already has 2 parents.")
return
parent = Person("Parent")
parent.children.append(person)
person.parents.append(parent)
self.draw_tree()
def delete_person(self, person):
# remove from spouse
if person.spouse:
person.spouse.spouse = None
# remove from parents' children list
for p in person.parents:
if person in p.children:
p.children.remove(person)
# remove from children's parents
for ch in person.children:
if person in ch.parents:
ch.parents.remove(person)
# If deleting the root person, reset to a new single person
if person is self.root_person:
self.root_person = Person()
self.draw_tree()
# ----- Canvas / Drawing -----
def draw_tree(self):
self.canvas.delete("all")
self.box_items.clear()
self.layout_positions = {}
self._assign_positions(self.root_person, 0, 0)
self._draw_connections(self.root_person)
self._draw_boxes(self.root_person)
def _assign_positions(self, person, level, offset):
# Recursive layout: compute x positions of children then center parents
if person in self.layout_positions:
return self.layout_positions[person][0]
if person.children:
child_xs = []
for ch in person.children:
child_xs.append(self._assign_positions(ch, level + 1, offset))
offset = child_xs[-1] + self.BOX_W + self.H_GAP
center_x = (child_xs[0] + child_xs[-1]) / 2
else:
center_x = offset
offset += self.BOX_W + self.H_GAP
self.layout_positions[person] = (center_x, level)
# Spouse should have same level and adjacent x
if person.spouse:
spouse_x = center_x + self.BOX_W + 10
self.layout_positions[person.spouse] = (spouse_x, level)
return center_x
def _draw_connections(self, person, drawn=set()):
# Draw spouse connection
if person.spouse and (person, person.spouse) not in drawn:
x, y = self._get_box_center(person)
sx, sy = self._get_box_center(person.spouse)
self.canvas.create_line(x, y, sx, sy)
drawn.add((person, person.spouse))
drawn.add((person.spouse, person))
# Draw children connections
for ch in person.children:
cx, cy = self._get_box_center(ch)
px, py = self._get_box_center(person)
self.canvas.create_line(px, py + self.BOX_H//2, cx, cy - self.BOX_H//2)
# If spouse exists, connect spouse too
if person.spouse:
sx, sy = self._get_box_center(person.spouse)
self.canvas.create_line(sx, sy + self.BOX_H//2, cx, cy - self.BOX_H//2)
self._draw_connections(ch, drawn)
def _draw_boxes(self, person, drawn=set()):
if person in drawn:
return
drawn.add(person)
x, level = self.layout_positions[person]
y = level * (self.BOX_H + self.V_GAP)
box = self.canvas.create_rectangle(
x, y, x + self.BOX_W, y + self.BOX_H, outline="black", fill="lightyellow"
)
text = self.canvas.create_text(
x + self.BOX_W/2, y + self.BOX_H/2, text=person.name
)
self.box_items[box] = person
self.box_items[text] = person
if person.spouse:
self._draw_boxes(person.spouse, drawn)
for ch in person.children:
self._draw_boxes(ch, drawn)
def _get_box_center(self, person):
x, level = self.layout_positions[person]
y = level * (self.BOX_H + self.V_GAP)
return (x + self.BOX_W/2, y + self.BOX_H/2)
# ----- Mouse click handler -----
def on_click(self, event):
item = self.canvas.find_closest(event.x, event.y)
person = self.box_items.get(item[0])
if person:
self.popup_menu(person, event.x_root, event.y_root)
def popup_menu(self, person, x, y):
menu = tk.Menu(self, tearoff=0)
menu.add_command(label="Edit name", command=lambda: self.edit_name(person))
menu.add_command(label="Add spouse", command=lambda: self.add_spouse(person))
menu.add_command(label="Add child", command=lambda: self.add_child(person))
menu.add_command(label="Add parent", command=lambda: self.add_parent(person))
menu.add_command(label="Delete", command=lambda: self.delete_person(person))
menu.tk_popup(x, y)
if __name__ == "__main__":
app = FamilyTreeApp()
app.mainloop()
Симулятор лавовой лампы
Промпт:
Создай программу на Python, которая использует только tkinter и стандартную библиотеку. Это должен быть симулятор лавовой лампы, показывающий капли, мягко плавающие по окну. Не используй круги, эллипсы или многоугольники для формы капель. Для контуров капель используй кривые Безье. Капли должны медленно сливаться и разделяться, как в настоящей лавовой лампе.
Результат: На лавовую лампу это не было похоже. Программы показывали движущиеся фигуры — и на этом всё. При сближении они грубо «сливались»: одна капля исчезала, а другая мгновенно увеличивалась в размере. Разделения не происходило; в некоторых приложениях мелкие капли возникали буквально из ниоткуда. Капли, как правило, дрожали, будто нервные чихуахуа.
Одна из LLM просто рисовала контуры капель.

fail_lavalamp_1.py
import tkinter as tk
import random
import math
WINDOW_WIDTH = 400
WINDOW_HEIGHT = 600
WAX_COLOR = "#f84d6a"
BACKGROUND_COLOR = "#101030"
NUM_SIDES = 12 # number of vertices per blob
WIGGLE_AMOUNT = 4 # how much each point can change per frame
class WaxBlob:
def __init__(self, canvas):
self.radius = random.randint(30, 60)
self.x = random.randint(self.radius, WINDOW_WIDTH - self.radius)
self.y = random.randint(self.radius, WINDOW_HEIGHT - self.radius)
self.vy = random.uniform(-0.8, 0.8)
self.canvas = canvas
# generate random radial offsets so shape isn't a perfect circle
self.offsets = [random.uniform(0.8, 1.2) for _ in range(NUM_SIDES)]
self.id = canvas.create_polygon(self._get_points(),
fill=WAX_COLOR, outline="")
def _get_points(self):
points = []
for i in range(NUM_SIDES):
angle = (2 * math.pi / NUM_SIDES) * i
r = self.radius * self.offsets[i]
px = self.x + r * math.cos(angle)
py = self.y + r * math.sin(angle)
points.extend([px, py])
return points
def move(self):
self.y += self.vy
if self.y - self.radius < 0 or self.y + self.radius > WINDOW_HEIGHT:
self.vy = -self.vy
# gently wiggle each radial offset so that the blob keeps changing shape
self.offsets = [
max(0.4, min(1.6, off + random.uniform(-0.02, 0.02)))
for off in self.offsets
]
self.canvas.coords(self.id, *self._get_points())
def distance_to(self, other):
return math.hypot(self.x - other.x, self.y - other.y)
def merge_with(self, other):
area1 = math.pi * (self.radius ** 2)
area2 = math.pi * (other.radius ** 2)
total = area1 + area2
self.radius = math.sqrt(total / math.pi)
self.offsets = [
(a + b) / 2 for a, b in zip(self.offsets, other.offsets)
]
self.x = (self.x + other.x) / 2
self.y = (self.y + other.y) / 2
def simulate():
for blob in blobs:
blob.move()
for i in range(len(blobs)):
for j in range(i + 1, len(blobs)):
b1 = blobs[i]
b2 = blobs[j]
if b1.distance_to(b2) < (b1.radius + b2.radius) * 0.65:
b1.merge_with(b2)
canvas.delete(b2.id)
blobs.remove(b2)
break
if random.random() < 0.01 and len(blobs) < 8:
new_blob = WaxBlob(canvas)
new_blob.radius = 15
blobs.append(new_blob)
root.after(30, simulate)
root = tk.Tk()
root.title("Lava Lamp Simulator")
canvas = tk.Canvas(root, width=WINDOW_WIDTH, height=WINDOW_HEIGHT,
bg=BACKGROUND_COLOR, highlightthickness=0)
canvas.pack()
blobs = [WaxBlob(canvas) for _ in range(4)]
root.after(30, simulate)
root.mainloop()
fail_lavalamp_2.py
import tkinter as tk
import math
import random
from datetime import datetime
class WaxBlob:
def __init__(self, x, y, radius, canvas_width, canvas_height):
self.x = x
self.y = y
self.base_radius = radius
self.vx = random.uniform(-0.3, 0.3)
self.vy = random.uniform(-0.8, -0.2)
self.canvas_width = canvas_width
self.canvas_height = canvas_height
self.density = radius * 0.1
self.temperature = random.uniform(0.5, 1.0)
self.age = 0
# Shape deformation parameters
self.oscillation_phase = random.uniform(0, 2 * math.pi)
self.oscillation_speed = random.uniform(0.02, 0.08)
self.deformation_strength = random.uniform(0.1, 0.3)
# Control points for blob shape (relative to center)
self.num_control_points = 6
self.control_points = []
self.control_phases = []
self.control_speeds = []
for i in range(self.num_control_points):
angle = (2 * math.pi * i) / self.num_control_points
self.control_points.append({
'base_angle': angle,
'radius_offset': random.uniform(0.8, 1.2),
'tangent_length': random.uniform(0.3, 0.7)
})
self.control_phases.append(random.uniform(0, 2 * math.pi))
self.control_speeds.append(random.uniform(0.01, 0.05))
# Flow deformation based on velocity
self.flow_memory = []
self.max_flow_memory = 5
@property
def radius(self):
return self.base_radius
def update(self):
self.age += 1
# Heat rises, cool sinks
heat_effect = (1.0 - self.y / self.canvas_height) * 0.3
buoyancy = (self.temperature + heat_effect - 0.5) * 0.02
# Gravity and buoyancy
self.vy += 0.005 - buoyancy
# Thermal currents
thermal_x = math.sin(self.y * 0.01 + self.age * 0.02) * 0.1
thermal_y = math.cos(self.x * 0.008 + self.age * 0.015) * 0.05
self.vx += thermal_x
self.vy += thermal_y
# Damping
self.vx *= 0.98
self.vy *= 0.995
# Store velocity for flow deformation
velocity_magnitude = math.sqrt(self.vx**2 + self.vy**2)
self.flow_memory.append(velocity_magnitude)
if len(self.flow_memory) > self.max_flow_memory:
self.flow_memory.pop(0)
# Update position
self.x += self.vx
self.y += self.vy
# Update shape oscillations
self.oscillation_phase += self.oscillation_speed
for i in range(len(self.control_phases)):
self.control_phases[i] += self.control_speeds[i]
# Boundary collision
if self.x - self.base_radius <= 0:
self.x = self.base_radius
self.vx = abs(self.vx) * 0.3 + random.uniform(0, 0.2)
elif self.x + self.base_radius >= self.canvas_width:
self.x = self.canvas_width - self.base_radius
self.vx = -abs(self.vx) * 0.3 - random.uniform(0, 0.2)
if self.y - self.base_radius <= 0:
self.y = self.base_radius
self.vy = abs(self.vy) * 0.4
self.temperature = max(0.2, self.temperature - 0.1)
elif self.y + self.base_radius >= self.canvas_height:
self.y = self.canvas_height - self.base_radius
self.vy = -abs(self.vy) * 0.4
self.temperature = min(1.0, self.temperature + 0.2)
def get_bezier_control_points(self):
"""Generate control points for bezier curves that form the blob shape"""
avg_velocity = sum(self.flow_memory) / max(len(self.flow_memory), 1)
velocity_angle = math.atan2(self.vy, self.vx)
control_points = []
for i in range(self.num_control_points):
cp = self.control_points[i]
# Base position
base_angle = cp['base_angle']
# Oscillation deformation
oscillation = math.sin(self.oscillation_phase + i * 0.8) * self.deformation_strength
# Individual point oscillation
point_oscillation = math.sin(self.control_phases[i]) * 0.2
# Flow-based deformation
flow_factor = avg_velocity * 0.8
angle_diff = abs(base_angle - velocity_angle)
angle_diff = min(angle_diff, 2 * math.pi - angle_diff)
if angle_diff < math.pi / 2:
flow_deformation = flow_factor * (1 - angle_diff / (math.pi / 2)) * 0.4
else:
flow_deformation = -flow_factor * 0.15
# Thermal deformation
thermal_deformation = self.temperature * 0.15 * math.sin(self.age * 0.03 + i * 1.2)
# Combined radius for this control point
radius_multiplier = (cp['radius_offset'] + oscillation + point_oscillation +
flow_deformation + thermal_deformation)
radius_multiplier = max(0.4, min(1.8, radius_multiplier))
point_radius = self.base_radius * radius_multiplier
# Main control point
x = self.x + math.cos(base_angle) * point_radius
y = self.y + math.sin(base_angle) * point_radius
# Tangent control points for bezier curves
tangent_length = self.base_radius * cp['tangent_length'] * radius_multiplier
tangent_angle1 = base_angle - math.pi / 2
tangent_angle2 = base_angle + math.pi / 2
# Add some flow-based tangent adjustment
flow_tangent_adjust = avg_velocity * 0.3
tangent_length *= (1 + flow_tangent_adjust)
tx1 = x + math.cos(tangent_angle1) * tangent_length
ty1 = y + math.sin(tangent_angle1) * tangent_length
tx2 = x + math.cos(tangent_angle2) * tangent_length
ty2 = y + math.sin(tangent_angle2) * tangent_length
control_points.append({
'point': (x, y),
'tangent1': (tx1, ty1),
'tangent2': (tx2, ty2)
})
return control_points
def distance_to(self, other):
return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
def can_merge_with(self, other):
distance = self.distance_to(other)
merge_distance = (self.base_radius + other.base_radius) * 0.8
return distance < merge_distance
def merge_with(self, other):
# Conservation of mass
total_area = math.pi * (self.base_radius**2 + other.base_radius**2)
new_radius = math.sqrt(total_area / math.pi)
# Weighted average
total_mass = self.base_radius**2 + other.base_radius**2
new_x = (self.x * self.base_radius**2 + other.x * other.base_radius**2) / total_mass
new_y = (self.y * self.base_radius**2 + other.y * other.base_radius**2) / total_mass
new_vx = (self.vx * self.base_radius**2 + other.vx * other.base_radius**2) / total_mass
new_vy = (self.vy * self.base_radius**2 + other.vy * other.base_radius**2) / total_mass
new_temp = (self.temperature + other.temperature) / 2
merged = WaxBlob(new_x, new_y, new_radius, self.canvas_width, self.canvas_height)
merged.vx = new_vx
merged.vy = new_vy
merged.temperature = new_temp
# Blend shape parameters
merged.oscillation_phase = (self.oscillation_phase + other.oscillation_phase) / 2
merged.oscillation_speed = (self.oscillation_speed + other.oscillation_speed) / 2
merged.deformation_strength = (self.deformation_strength + other.deformation_strength) / 2
return merged
def should_split(self):
if self.base_radius > 40:
return random.random() < 0.002
return False
def split(self):
split_ratio = random.uniform(0.3, 0.7)
area1 = math.pi * self.base_radius**2 * split_ratio
area2 = math.pi * self.base_radius**2 * (1 - split_ratio)
radius1 = math.sqrt(area1 / math.pi)
radius2 = math.sqrt(area2 / math.pi)
angle = random.uniform(0, 2 * math.pi)
offset = (radius1 + radius2) * 0.6
blob1 = WaxBlob(
self.x + math.cos(angle) * offset,
self.y + math.sin(angle) * offset,
radius1, self.canvas_width, self.canvas_height
)
blob2 = WaxBlob(
self.x - math.cos(angle) * offset,
self.y - math.sin(angle) * offset,
radius2, self.canvas_width, self.canvas_height
)
blob1.vx = self.vx + random.uniform(-0.5, 0.5)
blob1.vy = self.vy + random.uniform(-0.5, 0.5)
blob2.vx = self.vx + random.uniform(-0.5, 0.5)
blob2.vy = self.vy + random.uniform(-0.5, 0.5)
blob1.temperature = self.temperature + random.uniform(-0.1, 0.1)
blob2.temperature = self.temperature + random.uniform(-0.1, 0.1)
# Vary shape parameters
blob1.oscillation_speed = self.oscillation_speed + random.uniform(-0.02, 0.02)
blob2.oscillation_speed = self.oscillation_speed + random.uniform(-0.02, 0.02)
blob1.deformation_strength = self.deformation_strength + random.uniform(-0.1, 0.1)
blob2.deformation_strength = self.deformation_strength + random.uniform(-0.1, 0.1)
return [blob1, blob2]
class LavaLampSimulator:
def __init__(self, root):
self.root = root
self.root.title("Lava Lamp Simulator - Bezier Blobs")
self.root.geometry("400x600")
self.root.configure(bg='black')
# Create canvas
self.canvas = tk.Canvas(root, width=380, height=580, bg='#1a0d0d', highlightthickness=0)
self.canvas.pack(pady=10)
# Initialize blobs
self.blobs = []
self.create_initial_blobs()
# Start animation
self.animate()
def create_initial_blobs(self):
for _ in range(6):
x = random.uniform(50, 330)
y = random.uniform(100, 500)
radius = random.uniform(20, 35)
blob = WaxBlob(x, y, radius, 380, 580)
self.blobs.append(blob)
def get_blob_color(self, blob):
temp = blob.temperature
base_red = int(255 * (0.7 + temp * 0.3))
base_green = int(100 * temp)
base_blue = int(50 * temp)
height_factor = 1.0 - (blob.y / 580)
red = min(255, int(base_red * (0.8 + height_factor * 0.2)))
green = min(255, int(base_green * (0.8 + height_factor * 0.2)))
blue = min(255, int(base_blue * (0.8 + height_factor * 0.2)))
return f"#{red:02x}{green:02x}{blue:02x}"
def bezier_point(self, t, p0, p1, p2, p3):
"""Calculate a point on a cubic bezier curve"""
u = 1 - t
return (
u**3 * p0[0] + 3 * u**2 * t * p1[0] + 3 * u * t**2 * p2[0] + t**3 * p3[0],
u**3 * p0[1] + 3 * u**2 * t * p1[1] + 3 * u * t**2 * p2[1] + t**3 * p3[1]
)
def create_bezier_blob_outline(self, blob, resolution=20):
"""Create a smooth blob outline using bezier curves"""
control_points = blob.get_bezier_control_points()
outline_points = []
num_segments = len(control_points)
for i in range(num_segments):
# Current and next control point
curr = control_points[i]
next_i = (i + 1) % num_segments
next_cp = control_points[next_i]
# Define bezier curve from current point to next point
p0 = curr['point']
p1 = curr['tangent2'] # Outgoing tangent from current point
p2 = next_cp['tangent1'] # Incoming tangent to next point
p3 = next_cp['point']
# Generate points along the bezier curve
for j in range(resolution):
if i == num_segments - 1 and j == resolution - 1:
# Skip the last point to avoid duplication
break
t = j / resolution
point = self.bezier_point(t, p0, p1, p2, p3)
outline_points.extend([point[0], point[1]])
return outline_points
def draw_blob(self, blob):
color = self.get_blob_color(blob)
try:
# Get bezier outline points
outline_points = self.create_bezier_blob_outline(blob)
if len(outline_points) >= 6:
# Draw the main blob
self.canvas.create_polygon(outline_points, fill=color, outline='', smooth=True)
# Create highlight with scaled bezier shape
highlight_points = []
highlight_scale = 0.4
highlight_center_x = blob.x - blob.base_radius * 0.3
highlight_center_y = blob.y - blob.base_radius * 0.3
for i in range(0, len(outline_points), 2):
orig_x = outline_points[i]
orig_y = outline_points[i + 1]
# Vector from highlight center to point
vec_x = orig_x - highlight_center_x
vec_y = orig_y - highlight_center_y
# Scale toward highlight center
new_x = highlight_center_x + vec_x * highlight_scale
new_y = highlight_center_y + vec_y * highlight_scale
highlight_points.extend([new_x, new_y])
# Lighter color for highlight
r = min(255, int(color[1:3], 16) + 40)
g = min(255, int(color[3:5], 16) + 20)
b = min(255, int(color[5:7], 16) + 10)
highlight_color = f"#{r:02x}{g:02x}{b:02x}"
if len(highlight_points) >= 6:
self.canvas.create_polygon(highlight_points, fill=highlight_color,
outline='', smooth=True)
else:
self.draw_fallback_circle(blob, color)
except (tk.TclError, ValueError):
# Fallback to circle if bezier fails
self.draw_fallback_circle(blob, color)
def draw_fallback_circle(self, blob, color):
"""Fallback method to draw a simple circle"""
x1 = blob.x - blob.base_radius
y1 = blob.y - blob.base_radius
x2 = blob.x + blob.base_radius
y2 = blob.y + blob.base_radius
self.canvas.create_oval(x1, y1, x2, y2, fill=color, outline='')
def update_physics(self):
# Update all blobs
for blob in self.blobs:
blob.update()
# Handle merging
merged_blobs = []
used_indices = set()
for i, blob1 in enumerate(self.blobs):
if i in used_indices:
continue
merged = False
for j, blob2 in enumerate(self.blobs[i+1:], i+1):
if j in used_indices:
continue
if blob1.can_merge_with(blob2):
new_blob = blob1.merge_with(blob2)
merged_blobs.append(new_blob)
used_indices.add(i)
used_indices.add(j)
merged = True
break
if not merged:
merged_blobs.append(blob1)
self.blobs = merged_blobs
# Handle splitting
new_blobs = []
for blob in self.blobs:
if blob.should_split():
split_blobs = blob.split()
new_blobs.extend(split_blobs)
else:
new_blobs.append(blob)
self.blobs = new_blobs
# Maintain minimum number of blobs
if len(self.blobs) < 3:
x = random.uniform(50, 330)
y = random.uniform(400, 550)
radius = random.uniform(15, 25)
new_blob = WaxBlob(x, y, radius, 380, 580)
new_blob.temperature = 0.8
self.blobs.append(new_blob)
def animate(self):
# Clear canvas
self.canvas.delete("all")
# Update physics
self.update_physics()
# Draw all blobs
for blob in self.blobs:
self.draw_blob(blob)
# Schedule next frame
self.root.after(50, self.animate)
# Add the missing method to WaxBlob class
def get_bezier_control_points(self):
"""Generate control points for bezier curves that form the blob shape"""
avg_velocity = sum(self.flow_memory) / max(len(self.flow_memory), 1)
velocity_angle = math.atan2(self.vy, self.vx)
control_points = []
for i in range(self.num_control_points):
cp = self.control_points[i]
# Base position
base_angle = cp['base_angle']
# Oscillation deformation
oscillation = math.sin(self.oscillation_phase + i * 0.8) * self.deformation_strength
# Individual point oscillation
point_oscillation = math.sin(self.control_phases[i]) * 0.2
# Flow-based deformation
flow_factor = avg_velocity * 0.8
angle_diff = abs(base_angle - velocity_angle)
angle_diff = min(angle_diff, 2 * math.pi - angle_diff)
if angle_diff < math.pi / 2:
flow_deformation = flow_factor * (1 - angle_diff / (math.pi / 2)) * 0.4
else:
flow_deformation = -flow_factor * 0.15
# Thermal deformation
thermal_deformation = self.temperature * 0.15 * math.sin(self.age * 0.03 + i * 1.2)
# Combined radius for this control point
radius_multiplier = (cp['radius_offset'] + oscillation + point_oscillation +
flow_deformation + thermal_deformation)
radius_multiplier = max(0.4, min(1.8, radius_multiplier))
point_radius = self.base_radius * radius_multiplier
# Main control point position
x = self.x + math.cos(base_angle) * point_radius
y = self.y + math.sin(base_angle) * point_radius
# Calculate tangent control points for smooth bezier curves
tangent_length = self.base_radius * cp['tangent_length'] * radius_multiplier * 0.5
# Tangent direction (perpendicular to radius, with some variation)
tangent_base_angle = base_angle + math.pi / 2
tangent_variation = math.sin(self.control_phases[i] * 2) * 0.3
tangent_angle = tangent_base_angle + tangent_variation
# Flow influence on tangents
flow_influence = avg_velocity * 0.4
flow_angle_influence = math.sin(base_angle - velocity_angle) * flow_influence
tangent_angle += flow_angle_influence
# Tangent control points (before and after the main point)
tx1 = x - math.cos(tangent_angle) * tangent_length
ty1 = y - math.sin(tangent_angle) * tangent_length
tx2 = x + math.cos(tangent_angle) * tangent_length
ty2 = y + math.sin(tangent_angle) * tangent_length
control_points.append({
'point': (x, y),
'tangent1': (tx1, ty1),
'tangent2': (tx2, ty2)
})
return control_points
# Attach the method to the WaxBlob class
WaxBlob.get_bezier_control_points = get_bezier_control_points
if __name__ == "__main__":
root = tk.Tk()
app = LavaLampSimulator(root)
root.mainloop()
Симулятор снежного шара
Промпт:
Создай программу на Python, которая использует только tkinter и стандартную библиотеку. Программа — это “снежный шар” с синим фоном и белым “снегом” внутри. Когда окно двигают и “трясут”, снег тоже должен двигаться так, как это происходит в снежном шаре. Чем сильнее тряска, тем быстрее движется снег. Снежинки должны закручиваться по спирали внутри окна, а не летать, как отскакивающие пинг-понговые мячи.
Результат: У Claude был ограниченный успех: при перемещении окна «снежинки» действительно «тряслись», но вели себя скорее как пинг-понговые мячи в картонной коробке, а не как снег в шаре. Скорость и сила тряски не влияли на поведение: медленная и быстрая тряска давали один и тот же эффект.

fail_snowglobe_1.py
import tkinter as tk
import random
import time
WIDTH, HEIGHT = 400, 400
NUM_SNOWFLAKES = 200
GRAVITY = 0.2
FRICTION = 0.98
class Snowflake:
def __init__(self, canvas):
self.canvas = canvas
self.x = random.uniform(0, WIDTH)
self.y = random.uniform(0, HEIGHT)
self.vx = 0
self.vy = 0
self.id = canvas.create_oval(self.x, self.y, self.x+2, self.y+2, fill="white", outline="")
def update(self, shake_x, shake_y):
# Apply shake force to the velocity
self.vx += shake_x
self.vy += shake_y
# Apply gravity
self.vy += GRAVITY
# Move flake
self.x += self.vx
self.y += self.vy
# Stay in bounds (wrap horizontally, bounce vertically)
if self.x < 0:
self.x = WIDTH
elif self.x > WIDTH:
self.x = 0
# Top boundary: wrap to bottom
if self.y < 0:
self.y = HEIGHT
# Bottom boundary: bounce
if self.y > HEIGHT:
self.y = HEIGHT
self.vy *= -0.5 # bounce a bit
# Friction to slow flakes gradually
self.vx *= FRICTION
self.vy *= FRICTION
self.canvas.coords(self.id, self.x, self.y, self.x+2, self.y+2)
class SnowGlobe:
def __init__(self, root):
self.root = root
self.canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT, bg="skyblue")
self.canvas.pack()
self.snowflakes = [Snowflake(self.canvas) for _ in range(NUM_SNOWFLAKES)]
# Track window motion
self.prev_x = root.winfo_rootx()
self.prev_y = root.winfo_rooty()
self.shake_x = 0
self.shake_y = 0
root.bind("<Configure>", self.on_configure)
self.update_animation()
def on_configure(self, event):
cur_x = self.root.winfo_rootx()
cur_y = self.root.winfo_rooty()
dx = cur_x - self.prev_x
dy = cur_y - self.prev_y
self.prev_x = cur_x
self.prev_y = cur_y
# A little scaling so larger movements cause bigger shake
self.shake_x = dx * 0.2
self.shake_y = dy * 0.2
def update_animation(self):
# Apply shake to each snowflake
for snow in self.snowflakes:
snow.update(self.shake_x, self.shake_y)
# shake “dampens out†over time
self.shake_x *= 0.9
self.shake_y *= 0.9
self.root.after(16, self.update_animation)
if __name__ == "__main__":
root = tk.Tk()
root.title("Snow Globe")
globe = SnowGlobe(root)
root.mainloop()
fail_snowglobe_2.py
import tkinter as tk
import random
import math
import time
class SnowGlobe:
def __init__(self):
self.root = tk.Tk()
self.root.title("Snow Globe")
self.root.geometry("400x400")
self.root.configure(bg='lightblue')
# Canvas for drawing snow
self.canvas = tk.Canvas(self.root, width=380, height=380, bg='lightblue', highlightthickness=0)
self.canvas.pack(padx=10, pady=10)
# Snow particles
self.snowflakes = []
self.num_snowflakes = 100
# Window position tracking for shake detection
self.last_x = self.root.winfo_x()
self.last_y = self.root.winfo_y()
self.last_time = time.time()
self.velocity_x = 0
self.velocity_y = 0
self.shake_intensity = 0
# Physics constants
self.gravity = 0.1
self.friction = 0.98
self.bounce_damping = 0.7
self.create_snowflakes()
self.animate()
# Bind window movement events
self.root.bind('<Configure>', self.on_window_move)
def create_snowflakes(self):
"""Create initial snowflakes with random positions and properties"""
for _ in range(self.num_snowflakes):
snowflake = {
'x': random.uniform(5, 375),
'y': random.uniform(5, 375),
'vx': 0,
'vy': 0,
'size': random.uniform(2, 6),
'id': None
}
self.snowflakes.append(snowflake)
def on_window_move(self, event):
"""Track window movement to detect shaking"""
if event.widget == self.root:
current_time = time.time()
current_x = self.root.winfo_x()
current_y = self.root.winfo_y()
# Calculate time difference
dt = current_time - self.last_time
if dt > 0:
# Calculate velocity based on position change
self.velocity_x = (current_x - self.last_x) / dt
self.velocity_y = (current_y - self.last_y) / dt
# Calculate shake intensity based on velocity magnitude
velocity_magnitude = math.sqrt(self.velocity_x**2 + self.velocity_y**2)
self.shake_intensity = min(velocity_magnitude / 100, 10) # Cap at 10
# Apply shake force to snowflakes
self.apply_shake_force()
self.last_x = current_x
self.last_y = current_y
self.last_time = current_time
def apply_shake_force(self):
"""Apply forces to snowflakes based on window shaking"""
if self.shake_intensity > 0.1: # Only apply if significant movement
for snowflake in self.snowflakes:
# Add random component to make it more realistic
force_x = (self.velocity_x / 50) + random.uniform(-1, 1) * self.shake_intensity
force_y = (self.velocity_y / 50) + random.uniform(-1, 1) * self.shake_intensity
snowflake['vx'] += force_x
snowflake['vy'] += force_y
def update_snowflakes(self):
"""Update snowflake positions and handle physics"""
for snowflake in self.snowflakes:
# Apply gravity (always pulling down)
snowflake['vy'] += self.gravity
# Update positions
snowflake['x'] += snowflake['vx']
snowflake['y'] += snowflake['vy']
# Apply friction
snowflake['vx'] *= self.friction
snowflake['vy'] *= self.friction
# Boundary collisions with bounce
if snowflake['x'] <= snowflake['size']:
snowflake['x'] = snowflake['size']
snowflake['vx'] = -snowflake['vx'] * self.bounce_damping
elif snowflake['x'] >= 380 - snowflake['size']:
snowflake['x'] = 380 - snowflake['size']
snowflake['vx'] = -snowflake['vx'] * self.bounce_damping
if snowflake['y'] <= snowflake['size']:
snowflake['y'] = snowflake['size']
snowflake['vy'] = -snowflake['vy'] * self.bounce_damping
elif snowflake['y'] >= 380 - snowflake['size']:
snowflake['y'] = 380 - snowflake['size']
snowflake['vy'] = -snowflake['vy'] * self.bounce_damping
# Extra damping when hitting bottom (like settling)
snowflake['vx'] *= 0.8
def draw_snowflakes(self):
"""Draw all snowflakes on the canvas"""
self.canvas.delete("snowflake")
for snowflake in self.snowflakes:
x, y, size = snowflake['x'], snowflake['y'], snowflake['size']
# Draw snowflake as a white circle
self.canvas.create_oval(
x - size, y - size, x + size, y + size,
fill='white', outline='white', tags="snowflake"
)
# Add a sparkle effect for larger snowflakes
if size > 4:
self.canvas.create_oval(
x - 1, y - 1, x + 1, y + 1,
fill='lightcyan', outline='lightcyan', tags="snowflake"
)
def animate(self):
"""Main animation loop"""
self.update_snowflakes()
self.draw_snowflakes()
# Gradually reduce shake intensity over time
self.shake_intensity *= 0.95
# Schedule next frame
self.root.after(16, self.animate) # ~60 FPS
def run(self):
"""Start the snow globe application"""
self.root.mainloop()
if __name__ == "__main__":
snow_globe = SnowGlobe()
snow_globe.run()
Русскоязычное сообщество про Python

Друзья! Эту статью перевела команда Python for Devs — канала, где каждый день выходят самые свежие и полезные материалы о Python и его экосистеме. Подписывайтесь, чтобы ничего не пропустить!
Комментарии (3)
LaserPro
10.09.2025 07:59Этот "чел" зареган 25 августа, приглашён НЛО 29 августа и уже "написал" 28 статей.
Оно и есть "всё делается одноим промптом"
python_leader Автор
10.09.2025 07:59Это же перевод статьи. Если есть ошибки в переводе – говорите, поправим. Авторские материалы в разработке, скоро опубликуем
PKLab
Ну *бана нужно же самому хоть немного работать, если все будет делаться 1 промтом, нахер вы тогда не нужны, действительно.