diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8a8800 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +__pycache__/ +*.pyc +.env +.venv/ +venv/ +*.mp4 +*.png +*.jpg +*.jpeg +.DS_Store diff --git "a/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/compressed_\320\255\321\202\320\260\320\277 1.mp4" "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/compressed_\320\255\321\202\320\260\320\277 1.mp4" new file mode 100644 index 0000000..d6684c3 Binary files /dev/null and "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/compressed_\320\255\321\202\320\260\320\277 1.mp4" differ diff --git "a/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/desktop.py" "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/desktop.py" new file mode 100644 index 0000000..9327b83 --- /dev/null +++ "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/desktop.py" @@ -0,0 +1,744 @@ +import tkinter as tk +from tkinter import messagebox, scrolledtext +from typing import Optional, List, Iterator +import yaml +from enum import Enum +import random + + +class ТипНаправленийХимНапр(Enum): + ВперёдX = "ВперёдX" + НазадX = "НазадX" + ВлевоX = "ВлевоX" + ВправоX = "ВправоX" + ДиагXB = "ДиагXB" + ДиагXH = "ДиагXH" + + +class ТипЯчеекХимии(Enum): + Пусто = "Пусто" + Реактив = "Реактив" + Обработано = "Обработано" + Контейнер = "Контейнер" + Опасно = "Опасно" + Барьер = "Барьер" + Финиш = "Финиш" + + +class ЯчейкаРоботХимик: + def __init__( + self, + тип_ячейки: Optional[ТипЯчеекХимии] = None, + ячейка_робота: bool = False, + x: int = 0, + y: int = 0, + ) -> None: + self.ячейка_робота: bool = ячейка_робота + self.тип_ячейки: Optional[ТипЯчеекХимии] = тип_ячейки + self.x: int = x + self.y: int = y + + def __repr__(self) -> str: + return f"Ячейка({self.x},{self.y}):{self.тип_ячейки.name if self.тип_ячейки else None}" + + +class ЛабиринтРоботХимик: + def __init__( + self, + ширина: int = 5, + длина: int = 5, + ) -> None: + self.ширина: int = ширина + self.длина: int = длина + self.ячейки: List[List[ЯчейкаРоботХимик]] = [] + self.создать_случайный_лабиринт() + + def ПолучитьСоседнююЯчейку( + self, + текущая_ячейка: ЯчейкаРоботХимик, + направление_поиска: ТипНаправленийХимНапр, + ) -> Optional[ЯчейкаРоботХимик]: + if текущая_ячейка is None: + return None + if not self.ячейки: + return None + + x, y = текущая_ячейка.x, текущая_ячейка.y + + if направление_поиска == ТипНаправленийХимНапр.ВперёдX: + dx, dy = (0, 1) + elif направление_поиска == ТипНаправленийХимНапр.НазадX: + dx, dy = (0, -1) + elif направление_поиска == ТипНаправленийХимНапр.ВлевоX: + dx, dy = (-1, 0) + elif направление_поиска == ТипНаправленийХимНапр.ВправоX: + dx, dy = (1, 0) + elif направление_поиска == ТипНаправленийХимНапр.ДиагXB: + dx, dy = (-1, 1) + elif направление_поиска == ТипНаправленийХимНапр.ДиагXH: + dx, dy = (1, -1) + else: + return None + + nx, ny = x + dx, y + dy + + if not (0 <= nx < self.ширина and 0 <= ny < self.длина): + return None + + соседняя = self.ячейки[ny][nx] + + if соседняя.тип_ячейки in (ТипЯчеекХимии.Опасно, ТипЯчеекХимии.Барьер): + return None + + return соседняя + + def ПолучитьИтератор(self) -> Iterator[ЯчейкаРоботХимик]: + if not self.ячейки: + return iter(()) + + x = 0 + y = 0 + direction = 1 + + while True: + yield self.ячейки[y][x] + + if direction == 1: + if x < self.ширина - 1: + x += 1 + else: + if y == self.длина - 1: + break + y += 1 + direction = -1 + else: + if x > 0: + x -= 1 + else: + if y == self.длина - 1: + break + y += 1 + direction = 1 + + def создать_случайный_лабиринт(self) -> None: + self.ширина = 5 + self.длина = 5 + self.ячейки = [] + + for y in range(self.длина): + row = [] + for x in range(self.ширина): + row.append(ЯчейкаРоботХимик(тип_ячейки=ТипЯчеекХимии.Пусто, x=x, y=y)) + self.ячейки.append(row) + + ячейки_для_реактивов = random.sample( + [(x, y) for y in range(self.длина) for x in range(self.ширина) + if not (x == 0 and y == 0)], + min(3, self.ширина * self.длина - 1) + ) + + for x, y in ячейки_для_реактивов: + self.ячейки[y][x].тип_ячейки = ТипЯчеекХимии.Реактив + + опасные_ячейки = random.sample( + [(x, y) for y in range(self.длина) for x in range(self.ширина) + if not (x == 0 and y == 0) and self.ячейки[y][x].тип_ячейки == ТипЯчеекХимии.Пусто], + min(3, self.ширина * self.длина - 1 - len(ячейки_для_реактивов)) + ) + + for i, (x, y) in enumerate(опасные_ячейки): + if i % 2 == 0: + self.ячейки[y][x].тип_ячейки = ТипЯчеекХимии.Опасно + else: + self.ячейки[y][x].тип_ячейки = ТипЯчеекХимии.Барьер + + контейнеры = random.sample( + [(x, y) for y in range(self.длина) for x in range(self.ширина) + if not (x == 0 and y == 0) and self.ячейки[y][x].тип_ячейки == ТипЯчеекХимии.Пусто], + min(1, self.ширина * self.длина - 1 - len(ячейки_для_реактивов) - len(опасные_ячейки)) + ) + + if контейнеры: + x, y = контейнеры[0] + self.ячейки[y][x].тип_ячейки = ТипЯчеекХимии.Контейнер + + возможные_финиши = [(x, y) for y in range(self.длина) for x in range(self.ширина) + if not (x == 0 and y == 0) and self.ячейки[y][x].тип_ячейки == ТипЯчеекХимии.Пусто] + + if возможные_финиши: + x, y = random.choice(возможные_финиши) + self.ячейки[y][x].тип_ячейки = ТипЯчеекХимии.Финиш + + self.ячейки[0][0].ячейка_робота = True + + +class РоботХимик: + def __init__(self, лабиринт: ЛабиринтРоботХимик) -> None: + self.лабиринт: ЛабиринтРоботХимик = лабиринт + self.текущая_ячейка: Optional[ЯчейкаРоботХимик] = None + self.шаги = 0 + self.обработано_реактивов = 0 + self.обработано_пустот = 0 + + for row in self.лабиринт.ячейки: + for cell in row: + if cell.ячейка_робота: + self.текущая_ячейка = cell + break + if self.текущая_ячейка: + break + + if self.текущая_ячейка is None: + self.лабиринт.ячейки[0][0].ячейка_робота = True + self.текущая_ячейка = self.лабиринт.ячейки[0][0] + + def _переместить_робота(self, направление: ТипНаправленийХимНапр) -> Optional[ЯчейкаРоботХимик]: + if self.текущая_ячейка is None: + return None + + цель = self.лабиринт.ПолучитьСоседнююЯчейку(self.текущая_ячейка, направление) + + if цель is None: + return None + + self.текущая_ячейка.ячейка_робота = False + цель.ячейка_робота = True + self.текущая_ячейка = цель + self.шаги += 1 + + return цель + + def ДвигВперёд(self) -> Optional[ЯчейкаРоботХимик]: + return self._переместить_робота(ТипНаправленийХимНапр.ВперёдX) + + def Отодвинуть(self) -> Optional[ЯчейкаРоботХимик]: + return self._переместить_робота(ТипНаправленийХимНапр.НазадX) + + def СдвинутьВлево(self) -> Optional[ЯчейкаРоботХимик]: + return self._переместить_робота(ТипНаправленийХимНапр.ВлевоX) + + def СдвинутьВправо(self) -> Optional[ЯчейкаРоботХимик]: + return self._переместить_робота(ТипНаправленийХимНапр.ВправоX) + + def Подняться(self) -> Optional[ЯчейкаРоботХимик]: + return self._переместить_робота(ТипНаправленийХимНапр.ДиагXB) + + def Спуститься(self) -> Optional[ЯчейкаРоботХимик]: + return self._переместить_робота(ТипНаправленийХимНапр.ДиагXH) + + def Реактив(self) -> None: + if self.текущая_ячейка is None: + return + if self.текущая_ячейка.тип_ячейки == ТипЯчеекХимии.Реактив: + self.текущая_ячейка.тип_ячейки = ТипЯчеекХимии.Обработано + self.обработано_реактивов += 1 + + def Пусто(self) -> None: + if self.текущая_ячейка is None: + return + if self.текущая_ячейка.тип_ячейки == ТипЯчеекХимии.Пусто: + self.текущая_ячейка.тип_ячейки = ТипЯчеекХимии.Реактив + self.обработано_пустот += 1 + + def обработать_текущую_ячейку(self) -> None: + if self.текущая_ячейка is None: + return + + if self.текущая_ячейка.тип_ячейки == ТипЯчеекХимии.Реактив: + self.Реактив() + elif self.текущая_ячейка.тип_ячейки == ТипЯчеекХимии.Пусто: + self.Пусто() + self.Реактив() + + def проверить_цель(self) -> bool: + if self.текущая_ячейка is None: + return False + + for row in self.лабиринт.ячейки: + for cell in row: + if cell.тип_ячейки in [ТипЯчеекХимии.Реактив, ТипЯчеекХимии.Пусто]: + return False + + return self.текущая_ячейка.тип_ячейки == ТипЯчеекХимии.Финиш + + +class ChemistryApp: + def __init__(self, root): + self.root = root + self.root.title("Робот-Химик") + self.root.geometry("1200x700") + + self.condition_text = """ +УСЛОВИЕ ЗАДАЧИ: + +РоботХимик выполняет задачи в контексте 'Химии'. +Координатная система: ось Y направлена вверх (на север), +ось X направлена вправо (на восток). +Начало координат — в нижней левой ячейке. + +Методы специализации и их действие: +- Реактив: заменяет Реактив → Обработано +- Пусто: заменяет Пусто → Реактив + +Цель: +1. После завершения работы не должно остаться ячеек + следующих типов: Реактив, Пусто +2. Программа должна завершиться на ячейке типа: Финиш + +Ограничения перемещения: +- нельзя заходить на ячейки типа: Опасно +- нельзя заходить на ячейки типа: Барьер + +Управление: +- Используйте кнопки для движения и обработки ячеек +- Авто-режим: запускает движение змейкой с обработкой +- Проверка цели: проверяет выполнение условий +""" + + self.initialize_game() + + self.canvas_size = 90 + self.setup_ui() + + self.draw_grid() + self.log("Система инициализирована. Робот готов к работе.") + self.update_stats() + + def initialize_game(self): + self.maze = ЛабиринтРоботХимик(ширина=5, длина=5) + self.robot = РоботХимик(self.maze) + + self.iterator = iter(()) + self.auto_mode = False + + def setup_ui(self): + main_frame = tk.Frame(self.root) + main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + + left_frame = tk.Frame(main_frame) + left_frame.pack(side=tk.LEFT, padx=10) + + canvas_width = 5 * self.canvas_size + canvas_height = 5 * self.canvas_size + self.canvas = tk.Canvas(left_frame, width=canvas_width, height=canvas_height, + bg="#f0f0f0", relief=tk.FLAT) + self.canvas.pack() + + tk.Label(left_frame, text="Y (Север) ↑", font=("Arial", 10)).pack() + tk.Label(left_frame, text="X (Восток) →", font=("Arial", 10)).pack() + + center_frame = tk.Frame(main_frame) + center_frame.pack(side=tk.LEFT, fill=tk.Y, padx=20) + + stats_frame = tk.LabelFrame(center_frame, text="Статистика", padx=10, pady=10) + stats_frame.pack(fill=tk.X, pady=(0, 10)) + + self.stats_label = tk.Label(stats_frame, text="", font=("Arial", 10)) + self.stats_label.pack() + + control_frame = tk.LabelFrame(center_frame, text="Управление движением", padx=10, pady=10) + control_frame.pack(fill=tk.X, pady=(0, 10)) + + btn_style = {"width": 15, "height": 2, "font": ("Arial", 10)} + + diag_frame = tk.Frame(control_frame) + diag_frame.pack(pady=5) + tk.Button(diag_frame, text="Подняться", command=self.podnyatsya, + bg="#e6f2ff", **btn_style).pack(side=tk.LEFT, padx=2) + tk.Button(diag_frame, text="Спуститься", command=self.spustitsya, + bg="#e6f2ff", **btn_style).pack(side=tk.LEFT, padx=2) + + dir_frame = tk.Frame(control_frame) + dir_frame.pack(pady=5) + + tk.Button(dir_frame, text="Вперёд", command=self.dvig_vpered, + bg="#d9edf7", **btn_style).grid(row=0, column=1, padx=2, pady=2) + tk.Button(dir_frame, text="Влево", command=self.sdvinut_vlevo, + bg="#d9edf7", **btn_style).grid(row=1, column=0, padx=2, pady=2) + tk.Button(dir_frame, text="Обработать", command=self.obrabotat, + bg="#dff0d8", **btn_style).grid(row=1, column=1, padx=2, pady=2) + tk.Button(dir_frame, text="Вправо", command=self.sdvinut_vpravo, + bg="#d9edf7", **btn_style).grid(row=1, column=2, padx=2, pady=2) + tk.Button(dir_frame, text="Назад", command=self.otdodvinut, + bg="#d9edf7", **btn_style).grid(row=2, column=1, padx=2, pady=2) + + action_frame = tk.LabelFrame(center_frame, text="Действия с веществами", padx=10, pady=10) + action_frame.pack(fill=tk.X, pady=(0, 10)) + + tk.Button(action_frame, text="Реактив → Обработано", command=self.reaktiv, + bg="#f2dede", **btn_style).pack(pady=5) + tk.Button(action_frame, text="Пусто → Реактив", command=self.pusto, + bg="#fcf8e3", **btn_style).pack(pady=5) + + auto_frame = tk.LabelFrame(center_frame, text="Автоматический режим", padx=10, pady=10) + auto_frame.pack(fill=tk.X, pady=(0, 10)) + + tk.Button(auto_frame, text="Запустить змейку", command=self.start_snake, + bg="#dff0d8", **btn_style).pack(pady=5) + tk.Button(auto_frame, text="Остановить", command=self.stop_auto, + bg="#f2dede", **btn_style).pack(pady=5) + tk.Button(auto_frame, text="Проверить цель", command=self.proverit_cel, + bg="#d9edf7", **btn_style).pack(pady=5) + + tk.Button(center_frame, text="Начать заново", command=self.nachat_zanovo, + bg="#f8d7da", width=20, height=2, font=("Arial", 12)).pack(pady=20) + + right_frame = tk.Frame(main_frame) + right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10) + + condition_frame = tk.LabelFrame(right_frame, text="Условие задачи", padx=10, pady=10) + condition_frame.pack(fill=tk.BOTH, expand=True) + + self.condition_area = scrolledtext.ScrolledText(condition_frame, width=40, height=15, + font=("Arial", 9), wrap=tk.WORD) + self.condition_area.pack(fill=tk.BOTH, expand=True) + self.condition_area.insert(tk.END, self.condition_text) + self.condition_area.config(state=tk.DISABLED) + + log_frame = tk.LabelFrame(right_frame, text="Лог действий", padx=10, pady=10) + log_frame.pack(fill=tk.BOTH, expand=True) + + self.log_area = scrolledtext.ScrolledText(log_frame, width=40, height=10, + font=("Courier", 9)) + self.log_area.pack(fill=tk.BOTH, expand=True) + + def draw_grid(self): + self.canvas.delete("all") + + for y in range(self.maze.длина): + for x in range(self.maze.ширина): + cell = self.maze.ячейки[y][x] + + canvas_y = (self.maze.длина - 1 - y) * self.canvas_size + canvas_x = x * self.canvas_size + + color = self.get_cell_color(cell.тип_ячейки) + + self.canvas.create_rectangle( + canvas_x, canvas_y, + canvas_x + self.canvas_size, canvas_y + self.canvas_size, + fill=color, outline="#333333", width=1 + ) + + self.canvas.create_rectangle( + canvas_x, canvas_y, + canvas_x + self.canvas_size, canvas_y + self.canvas_size, + outline="#cccccc", width=1 + ) + + coord_text = f"{x},{y}" + self.canvas.create_text( + canvas_x + 20, canvas_y + 20, + text=coord_text, font=("Arial", 9), fill="#666666" + ) + + if cell.тип_ячейки: + type_text = cell.тип_ячейки.value[:3] + self.canvas.create_text( + canvas_x + self.canvas_size // 2, + canvas_y + self.canvas_size // 2, + text=type_text, font=("Arial", 11, "bold"), fill="#333333" + ) + + if cell.ячейка_робота: + center_x = canvas_x + self.canvas_size // 2 + center_y = canvas_y + self.canvas_size // 2 + radius = self.canvas_size // 3 + + self.canvas.create_oval( + center_x - radius, center_y - radius, + center_x + radius, center_y + radius, + fill="#4a86e8", outline="#1c4587", width=2 + ) + + self.canvas.create_text( + center_x, center_y, + text="R", font=("Arial", 14, "bold"), fill="white" + ) + + self.canvas.create_line( + center_x, center_y + radius // 2, + center_x, center_y - radius // 2, + arrow=tk.LAST, width=2, fill="white" + ) + + def get_cell_color(self, cell_type): + colors = { + ТипЯчеекХимии.Пусто: "#ffffff", + ТипЯчеекХимии.Реактив: "#d9ead3", + ТипЯчеекХимии.Обработано: "#c9daf8", + ТипЯчеекХимии.Контейнер: "#f3f3f3", + ТипЯчеекХимии.Опасно: "#f4cccc", + ТипЯчеекХимии.Барьер: "#999999", + ТипЯчеекХимии.Финиш: "#fff2cc", + } + return colors.get(cell_type, "#ffffff") + + def log(self, message): + self.log_area.insert(tk.END, message + "\n") + self.log_area.see(tk.END) + + def update_stats(self): + if self.robot.текущая_ячейка: + stats_text = ( + f"Шагов: {self.robot.шаги}\n" + f"Обработано реактивов: {self.robot.обработано_реактивов}\n" + f"Обработано пустот: {self.robot.обработано_пустот}\n" + f"Текущая позиция: ({self.robot.текущая_ячейка.x},{self.robot.текущая_ячейка.y})\n" + f"Тип ячейки: {self.robot.текущая_ячейка.тип_ячейки.value}" + ) + self.stats_label.config(text=stats_text) + + def dvig_vpered(self): + result = self.robot.ДвигВперёд() + if result: + self.log(f"ДвигВперёд → ({result.x},{result.y})") + self.draw_grid() + self.update_stats() + else: + self.log("ДвигВперёд: движение невозможно!") + + def otdodvinut(self): + result = self.robot.Отодвинуть() + if result: + self.log(f"Отодвинуть → ({result.x},{result.y})") + self.draw_grid() + self.update_stats() + else: + self.log("Отодвинуть: движение невозможно!") + + def sdvinut_vlevo(self): + result = self.robot.СдвинутьВлево() + if result: + self.log(f"СдвинутьВлево → ({result.x},{result.y})") + self.draw_grid() + self.update_stats() + else: + self.log("СдвинутьВлево: движение невозможно!") + + def sdvinut_vpravo(self): + result = self.robot.СдвинутьВправо() + if result: + self.log(f"СдвинутьВправо → ({result.x},{result.y})") + self.draw_grid() + self.update_stats() + else: + self.log("СдвинутьВправо: движение невозможно!") + + def podnyatsya(self): + result = self.robot.Подняться() + if result: + self.log(f"Подняться → ({result.x},{result.y})") + self.draw_grid() + self.update_stats() + else: + self.log("Подняться: движение невозможно!") + + def spustitsya(self): + result = self.robot.Спуститься() + if result: + self.log(f"Спуститься → ({result.x},{result.y})") + self.draw_grid() + self.update_stats() + else: + self.log("Спуститься: движение невозможно!") + + def reaktiv(self): + self.robot.Реактив() + self.log(f"Реактив → Обработано в ({self.robot.текущая_ячейка.x},{self.robot.текущая_ячейка.y})") + self.draw_grid() + self.update_stats() + + def pusto(self): + self.robot.Пусто() + self.log(f"Пусто → Реактив в ({self.robot.текущая_ячейка.x},{self.robot.текущая_ячейка.y})") + self.draw_grid() + self.update_stats() + + def obrabotat(self): + old_type = self.robot.текущая_ячейка.тип_ячейки + self.robot.обработать_текущую_ячейку() + new_type = self.robot.текущая_ячейка.тип_ячейки + + if old_type != new_type: + self.log(f"Обработано: {old_type.value} → {new_type.value}") + else: + self.log(f"Ячейка не требует обработки: {old_type.value}") + + self.draw_grid() + self.update_stats() + + def start_snake(self): + self.iterator = self.maze.ПолучитьИтератор() + self.auto_mode = True + self.log("Запущен автоматический режим (змейка)") + self.auto_step() + + def auto_step(self): + if not self.auto_mode: + return + + try: + next_cell = next(self.iterator) + + if (next_cell.x == self.robot.текущая_ячейка.x and + next_cell.y == self.robot.текущая_ячейка.y): + next_cell = next(self.iterator) + + dx = next_cell.x - self.robot.текущая_ячейка.x + dy = next_cell.y - self.robot.текущая_ячейка.y + + result = None + if dx == 1 and dy == 0: + result = self.robot.СдвинутьВправо() + elif dx == -1 and dy == 0: + result = self.robot.СдвинутьВлево() + elif dx == 0 and dy == 1: + result = self.robot.ДвигВперёд() + elif dx == 0 and dy == -1: + result = self.robot.Отодвинуть() + elif dx == -1 and dy == 1: + result = self.robot.Подняться() + elif dx == 1 and dy == -1: + result = self.robot.Спуститься() + + if result: + self.robot.обработать_текущую_ячейку() + self.log(f"Авто-шаг → ({result.x},{result.y})") + self.draw_grid() + self.update_stats() + + self.root.after(500, self.auto_step) + else: + self.log("Авто-шаг: движение невозможно!") + self.auto_mode = False + + except StopIteration: + self.log("Автоматический режим завершен: пройдены все ячейки") + self.auto_mode = False + + def stop_auto(self): + self.auto_mode = False + self.log("Автоматический режим остановлен") + + def proverit_cel(self): + if self.robot.проверить_цель(): + messagebox.showinfo("Цель достигнута!", + "Поздравляем! Робот успешно выполнил задачу:\n" + "1. Все реактивы и пустоты обработаны\n" + "2. Робот находится на финише") + self.log("Цель достигнута!") + else: + remaining = [] + for row in self.maze.ячейки: + for cell in row: + if cell.тип_ячейки == ТипЯчеекХимии.Реактив: + remaining.append(f"Реактив в ({cell.x},{cell.y})") + elif cell.тип_ячейки == ТипЯчеекХимии.Пусто: + remaining.append(f"Пусто в ({cell.x},{cell.y})") + + if remaining: + message = "Цель не достигнута!\nНе обработано:\n" + "\n".join(remaining[:5]) + if len(remaining) > 5: + message += f"\n... и ещё {len(remaining) - 5}" + messagebox.showwarning("Цель не достигнута", message) + elif self.robot.текущая_ячейка.тип_ячейки != ТипЯчеекХимии.Финиш: + messagebox.showwarning("Цель не достигнута", + "Робот не на финише!\n" + f"Текущая позиция: ({self.robot.текущая_ячейка.x},{self.robot.текущая_ячейка.y})") + else: + messagebox.showinfo("Проверка", "Все условия выполнены, кроме нахождения на финише") + + def nachat_zanovo(self): + self.stop_auto() + self.initialize_game() + self.log("=" * 50) + self.log("НОВАЯ ИГРА: создан случайный лабиринт 5x5") + self.log("Робот начинает в позиции (0,0)") + self.log("=" * 50) + self.draw_grid() + self.update_stats() + + +def create_mapping_yaml(): + mapping = { + 'классы': [ + { + 'Класс диаграммы': 'РоботХимик', + 'свойства': [ + {'лабиринт': 'лабиринт'} + ], + 'методы': [ + {'ДвигВперёд': 'ДвигВперёд'}, + {'Отодвинуть': 'Отодвинуть'}, + {'СдвинутьВлево': 'СдвинутьВлево'}, + {'СдвинутьВправо': 'СдвинутьВправо'}, + {'Подняться': 'Подняться'}, + {'Спуститься': 'Спуститься'}, + {'Реактив': 'Реактив'}, + {'Пусто': 'Пусто'} + ] + }, + { + 'Класс диаграммы': 'ЛабиринтРоботХимик', + 'свойства': [ + {'ширина': 'ширина'}, + {'длина': 'длина'}, + {'ячейки': 'ячейки'} + ], + 'методы': [ + {'ПолучитьСоседнююЯчейку': 'ПолучитьСоседнююЯчейку'}, + {'ПолучитьИтератор': 'ПолучитьИтератор'}, + {'ИнициализироватьЛабиринт': 'ИнициализироватьЛабиринт'} + ] + }, + { + 'Класс диаграммы': 'ЯчейкаРоботХимик', + 'свойства': [ + {'ячейка_робота': 'ячейка_робота'}, + {'тип_ячейки': 'тип_ячейки'} + ], + 'методы': [] + } + ], + 'перечисления': [ + { + 'ПеречислениеНаДиаграмме': 'ТипНаправленийХимНапр', + 'опции': [ + {'ВперёдX': 'ВперёдX'}, + {'НазадX': 'НазадX'}, + {'ВлевоX': 'ВлевоX'}, + {'ВправоX': 'ВправоX'}, + {'ДиагXB': 'ДиагXB'}, + {'ДиагXH': 'ДиагXH'} + ] + }, + { + 'ПеречислениеНаДиаграмме': 'ТипЯчеекХимии', + 'опции': [ + {'Пусто': 'Пусто'}, + {'Реактив': 'Реактив'}, + {'Обработано': 'Обработано'}, + {'Контейнер': 'Контейнер'}, + {'Опасно': 'Опасно'}, + {'Барьер': 'Барьер'}, + {'Финиш': 'Финиш'} + ] + } + ] + } + + with open('mapping.yaml', 'w', encoding='utf-8') as f: + yaml.dump(mapping, f, allow_unicode=True, default_flow_style=False) + + +def main(): + create_mapping_yaml() + + root = tk.Tk() + app = ChemistryApp(root) + root.mainloop() + + +if __name__ == "__main__": + main() diff --git "a/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/etap-2_nkfXVHBU.mp4" "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/etap-2_nkfXVHBU.mp4" new file mode 100644 index 0000000..f06e737 Binary files /dev/null and "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/etap-2_nkfXVHBU.mp4" differ diff --git "a/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/image.png" "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/image.png" new file mode 100644 index 0000000..e8402c8 Binary files /dev/null and "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/image.png" differ diff --git "a/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/main.py" "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/main.py" new file mode 100644 index 0000000..8a30ea8 --- /dev/null +++ "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/main.py" @@ -0,0 +1,198 @@ +from __future__ import annotations + +from enum import Enum +from typing import List, Iterator, Optional + + +class ТипНаправленийХимНапр(Enum): + ВперёдX = "ВперёдX" + НазадX = "НазадX" + ВлевоX = "ВлевоX" + ВправоX = "ВправоX" + ДиагXB = "ДиагXB" + ДиагXH = "ДиагXH" + + +class ТипЯчеекХимии(Enum): + Пусто = "Пусто" + Реактив = "Реактив" + Обработано = "Обработано" + Контейнер = "Контейнер" + Опасно = "Опасно" + Барьер = "Барьер" + Финиш = "Финиш" + + +class ЯчейкаРоботХимик: + def __init__( + self, + тип_ячейки: Optional[ТипЯчеекХимии] = None, + ячейка_робота: bool = False, + x: int = 0, + y: int = 0, + ) -> None: + self.ячейка_робота: bool = ячейка_робота + self.тип_ячейки: Optional[ТипЯчеекХимии] = тип_ячейки + self.x: int = x + self.y: int = y + + def __repr__(self) -> str: + return f"Ячейка({self.x},{self.y}):{self.тип_ячейки.name if self.тип_ячейки else None}" + + +class ЛабиринтРоботХимик: + def __init__( + self, + ширина: int = 0, + длина: int = 0, + ) -> None: + self.ширина: int = ширина + self.длина: int = длина + self.ячейки: List[List[ЯчейкаРоботХимик]] = [] + + def ПолучитьСоседнююЯчейку( + self, + текущая_ячейка: ЯчейкаРоботХимик, + направление_поиска: ТипНаправленийХимНапр, + ) -> Optional[ЯчейкаРоботХимик]: + if текущая_ячейка is None: + return None + + if not self.ячейки: + return None + + x, y = текущая_ячейка.x, текущая_ячейка.y + + if направление_поиска == ТипНаправленийХимНапр.ВперёдX: + dx, dy = (0, -1) + elif направление_поиска == ТипНаправленийХимНапр.НазадX: + dx, dy = (0, 1) + elif направление_поиска == ТипНаправленийХимНапр.ВлевоX: + dx, dy = (-1, 0) + elif направление_поиска == ТипНаправленийХимНапр.ВправоX: + dx, dy = (1, 0) + elif направление_поиска == ТипНаправленийХимНапр.ДиагXB: + dx, dy = (-1, -1) + elif направление_поиска == ТипНаправленийХимНапр.ДиагXH: + dx, dy = (1, 1) + else: + return None + + nx, ny = x + dx, y + dy + + if not (0 <= nx < self.ширина and 0 <= ny < self.длина): + return None + + соседняя = self.ячейки[ny][nx] + + if соседняя.тип_ячейки in (ТипЯчеекХимии.Опасно, ТипЯчеекХимии.Барьер): + return None + + return соседняя + + def ПолучитьИтератор(self) -> Iterator[ЯчейкаРоботХимик]: + if not self.ячейки: + return iter(()) + + x = 0 + y = self.длина - 1 + direction = 1 + + while True: + yield self.ячейки[y][x] + + if direction == 1: + if x < self.ширина - 1: + x += 1 + else: + if y == 0: + break + y -= 1 + direction = -1 + else: # движемся влево + if x > 0: + x -= 1 + else: + if y == 0: + break + y -= 1 + direction = 1 + + def ИнициализироватьЛабиринт( + self, + тип_ячейки: ТипЯчеекХимии, + ) -> None: + # Создаем ячейки + self.ячейки = [] + for y in range(self.длина): + row = [] + for x in range(self.ширина): + row.append(ЯчейкаРоботХимик(тип_ячейки=тип_ячейки, x=x, y=y)) + self.ячейки.append(row) + + +class РоботХимик: + def __init__(self, лабиринт: ЛабиринтРоботХимик) -> None: + self.лабиринт: ЛабиринтРоботХимик = лабиринт + self.текущая_ячейка: Optional[ЯчейкаРоботХимик] = None + + # Находим робота в лабиринте + for row in self.лабиринт.ячейки: + for cell in row: + if cell.ячейка_робота: + self.текущая_ячейка = cell + return + + # Если робот не найден, ставим его в первую доступную ячейку + if self.текущая_ячейка is None: + for row in self.лабиринт.ячейки: + for cell in row: + if cell.тип_ячейки not in (ТипЯчеекХимии.Опасно, ТипЯчеекХимии.Барьер): + cell.ячейка_робота = True + self.текущая_ячейка = cell + return + + def _переместить_робота(self, направление: ТипНаправленийХимНапр) -> Optional[ЯчейкаРоботХимик]: + if self.текущая_ячейка is None: + return None + + цель = self.лабиринт.ПолучитьСоседнююЯчейку(self.текущая_ячейка, направление) + + if цель is None: + return None + + # Перемещаем робота + self.текущая_ячейка.ячейка_робота = False + цель.ячейка_робота = True + self.текущая_ячейка = цель + + return цель + + def ДвигВперёд(self) -> Optional[ЯчейкаРоботХимик]: + return self._переместить_робота(ТипНаправленийХимНапр.ВперёдX) + + def Отодвинуть(self) -> Optional[ЯчейкаРоботХимик]: + return self._переместить_робота(ТипНаправленийХимНапр.НазадX) + + def СдвинутьВлево(self) -> Optional[ЯчейкаРоботХимик]: + return self._переместить_робота(ТипНаправленийХимНапр.ВлевоX) + + def СдвинутьВправо(self) -> Optional[ЯчейкаРоботХимик]: + return self._переместить_робота(ТипНаправленийХимНапр.ВправоX) + + def Подняться(self) -> Optional[ЯчейкаРоботХимик]: + return self._переместить_робота(ТипНаправленийХимНапр.ДиагXB) + + def Спуститься(self) -> Optional[ЯчейкаРоботХимик]: + return self._переместить_робота(ТипНаправленийХимНапр.ДиагXH) + + def Реактив(self) -> None: + if self.текущая_ячейка is None: + return + self.текущая_ячейка.тип_ячейки = ТипЯчеекХимии.Обработано + + def Пусто(self) -> None: + if self.текущая_ячейка is None: + return + + self.текущая_ячейка.тип_ячейки = ТипЯчеекХимии.Реактив diff --git "a/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/mapping.yaml" "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/mapping.yaml" new file mode 100644 index 0000000..fbfe350 --- /dev/null +++ "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/mapping.yaml" @@ -0,0 +1,45 @@ +классы: +- Класс диаграммы: РоботХимик + методы: + - ДвигВперёд: ДвигВперёд + - Отодвинуть: Отодвинуть + - СдвинутьВлево: СдвинутьВлево + - СдвинутьВправо: СдвинутьВправо + - Подняться: Подняться + - Спуститься: Спуститься + - Реактив: Реактив + - Пусто: Пусто + свойства: + - лабиринт: лабиринт +- Класс диаграммы: ЛабиринтРоботХимик + методы: + - ПолучитьСоседнююЯчейку: ПолучитьСоседнююЯчейку + - ПолучитьИтератор: ПолучитьИтератор + - ИнициализироватьЛабиринт: ИнициализироватьЛабиринт + свойства: + - ширина: ширина + - длина: длина + - ячейки: ячейки +- Класс диаграммы: ЯчейкаРоботХимик + методы: [] + свойства: + - ячейка_робота: ячейка_робота + - тип_ячейки: тип_ячейки +перечисления: +- ПеречислениеНаДиаграмме: ТипНаправленийХимНапр + опции: + - ВперёдX: ВперёдX + - НазадX: НазадX + - ВлевоX: ВлевоX + - ВправоX: ВправоX + - ДиагXB: ДиагXB + - ДиагXH: ДиагXH +- ПеречислениеНаДиаграмме: ТипЯчеекХимии + опции: + - Пусто: Пусто + - Реактив: Реактив + - Обработано: Обработано + - Контейнер: Контейнер + - Опасно: Опасно + - Барьер: Барьер + - Финиш: Финиш diff --git "a/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/oaoa.md" "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/oaoa.md" new file mode 100644 index 0000000..21bbc51 --- /dev/null +++ "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/oaoa.md" @@ -0,0 +1,40 @@ +# ТЗ: +Робот: РоботХимик + +Описание: +РоботХимик выполняет задачи в контексте 'Химии'. +Координатная система: ось Y направлена вверх (на север), ось X направлена вправо (на восток). Начало координат — в нижней левой ячейке. + +Файлы диаграмм: +- robot.png - диаграмма классов робота для реализации и сопоставления +- dir1.png и dir2.png — диаграммы соответствия методов движения и направлений +- iterator.png — модель итератора (зигзаг: вправо, вверх, влево, вверх, вправо...) + +Методы специализации и их действие (явно): +- Реактив: заменяет Реактив → Обработано +- Пусто: заменяет Пусто → Реактив + +Цель: +- После завершения работы не должно остаться ячеек следующих типов: + - Реактив + - Пусто +- Программа должна завершиться на ячейке типа: Финиш + +Ограничения перемещения: +- нельзя заходить на ячейки типа: Опасно +- нельзя заходить на ячейки типа: Барьер + +В коде использовать только enum.value при передаче в функции. Пример: ИмяПеречисления.Опция.value + +После завершения работы вам нужно создать файл mapping.yaml следующего вида: +классы: + - Класс диаграммы: ВашКлассВПитоне + свойства: + - свойство_класса_в_диаграмме: свойство_класса_в_коде + методы: + - метод_класса_в_диаграмме: метод_класса_в_коде +перечисления: + - ПеречислениеНаДиаграмме: ПеречислениеВКоде + опции: + - опция_на_диаграмме: опция_в_коде +Весь необходимый код должен быть в файлу main.py. Необходимо отправить mapping.yaml и main.py на проверку преподавателю. diff --git "a/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/web.py" "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/web.py" new file mode 100644 index 0000000..a0889f1 --- /dev/null +++ "b/5030102_30202/\320\250\320\270\320\273\320\276\320\262_\320\237\320\260\320\262\320\265\320\273_\320\245\320\270\320\274\320\270\320\272/web.py" @@ -0,0 +1,693 @@ +import streamlit as st +import random +import yaml +from enum import Enum + + +class ТипНаправленийХимНапр(Enum): + ВперёдX = "ВперёдX" + НазадX = "НазадX" + ВлевоX = "ВлевоX" + ВправоX = "ВправоX" + ДиагXB = "ДиагXB" + ДиагXH = "ДиагXH" + + +class ТипЯчеекХимии(Enum): + Пусто = "Пусто" + Реактив = "Реактив" + Обработано = "Обработано" + Контейнер = "Контейнер" + Опасно = "Опасно" + Барьер = "Барьер" + Финиш = "Финиш" + +class ЯчейкаРоботХимик: + def __init__(self, тип_ячейки=None, ячейка_робота=False, x=0, y=0): + self.ячейка_робота = ячейка_робота + self.тип_ячейки = тип_ячейки + self.x = x + self.y = y + + +class ЛабиринтРоботХимик: + def __init__(self, ширина=5, длина=5): + self.ширина = ширина + self.длина = длина + self.ячейки = [] + self.создать_случайный_лабиринт() + + def ПолучитьСоседнююЯчейку(self, текущая_ячейка, направление_поиска): + if текущая_ячейка is None or not self.ячейки: + return None + + x, y = текущая_ячейка.x, текущая_ячейка.y + + if направление_поиска == ТипНаправленийХимНапр.ВперёдX: + dx, dy = (0, 1) + elif направление_поиска == ТипНаправленийХимНапр.НазадX: + dx, dy = (0, -1) + elif направление_поиска == ТипНаправленийХимНапр.ВлевоX: + dx, dy = (-1, 0) + elif направление_поиска == ТипНаправленийХимНапр.ВправоX: + dx, dy = (1, 0) + elif направление_поиска == ТипНаправленийХимНапр.ДиагXB: + dx, dy = (-1, 1) + elif направление_поиска == ТипНаправленийХимНапр.ДиагXH: + dx, dy = (1, -1) + else: + return None + + nx, ny = x + dx, y + dy + + if not (0 <= nx < self.ширина and 0 <= ny < self.длина): + return None + + соседняя = self.ячейки[ny][nx] + + if соседняя.тип_ячейки in (ТипЯчеекХимии.Опасно, ТипЯчеекХимии.Барьер): + return None + + return соседняя + + def ПолучитьИтератор(self): + if not self.ячейки: + return iter(()) + + x = 0 + y = 0 + direction = 1 + + while True: + yield self.ячейки[y][x] + + if direction == 1: + if x < self.ширина - 1: + x += 1 + else: + if y == self.длина - 1: + break + y += 1 + direction = -1 + else: + if x > 0: + x -= 1 + else: + if y == self.длина - 1: + break + y += 1 + direction = 1 + + def создать_случайный_лабиринт(self): + self.ячейки = [] + + for y in range(self.длина): + row = [] + for x in range(self.ширина): + row.append(ЯчейкаРоботХимик(тип_ячейки=ТипЯчеекХимии.Пусто, x=x, y=y)) + self.ячейки.append(row) + + ячейки_для_реактивов = random.sample( + [(x, y) for y in range(self.длина) for x in range(self.ширина) + if not (x == 0 and y == 0)], + min(3, self.ширина * self.длина - 1) + ) + + for x, y in ячейки_для_реактивов: + self.ячейки[y][x].тип_ячейки = ТипЯчеекХимии.Реактив + + опасные_ячейки = random.sample( + [(x, y) for y in range(self.длина) for x in range(self.ширина) + if not (x == 0 and y == 0) and self.ячейки[y][x].тип_ячейки == ТипЯчеекХимии.Пусто], + min(3, self.ширина * self.длина - 1 - len(ячейки_для_реактивов)) + ) + + for i, (x, y) in enumerate(опасные_ячейки): + if i % 2 == 0: + self.ячейки[y][x].тип_ячейки = ТипЯчеекХимии.Опасно + else: + self.ячейки[y][x].тип_ячейки = ТипЯчеекХимии.Барьер + + контейнеры = random.sample( + [(x, y) for y in range(self.длина) for x in range(self.ширина) + if not (x == 0 and y == 0) and self.ячейки[y][x].тип_ячейки == ТипЯчеекХимии.Пусто], + min(1, self.ширина * self.длина - 1 - len(ячейки_для_реактивов) - len(опасные_ячейки)) + ) + + if контейнеры: + x, y = контейнеры[0] + self.ячейки[y][x].тип_ячейки = ТипЯчеекХимии.Контейнер + + возможные_финиши = [(x, y) for y in range(self.длина) for x in range(self.ширина) + if not (x == 0 and y == 0) and self.ячейки[y][x].тип_ячейки == ТипЯчеекХимии.Пусто] + + if возможные_финиши: + x, y = random.choice(возможные_финиши) + self.ячейки[y][x].тип_ячейки = ТипЯчеекХимии.Финиш + + self.ячейки[0][0].ячейка_робота = True + + +class РоботХимик: + def __init__(self, лабиринт): + self.лабиринт = лабиринт + self.шаги = 0 + self.обработано_реактивов = 0 + self.обработано_пустот = 0 + + for row in self.лабиринт.ячейки: + for cell in row: + if cell.ячейка_робота: + self.текущая_ячейка = cell + return + + self.лабиринт.ячейки[0][0].ячейка_робота = True + self.текущая_ячейка = self.лабиринт.ячейки[0][0] + + def _переместить_робота(self, направление): + if self.текущая_ячейка is None: + return None + + цель = self.лабиринт.ПолучитьСоседнююЯчейку(self.текущая_ячейка, направление) + + if цель is None: + return None + + self.текущая_ячейка.ячейка_робота = False + цель.ячейка_робота = True + self.текущая_ячейка = цель + self.шаги += 1 + + return цель + + def ДвигВперёд(self): + return self._переместить_робота(ТипНаправленийХимНапр.ВперёдX) + + def Отодвинуть(self): + return self._переместить_робота(ТипНаправленийХимНапр.НазадX) + + def СдвинутьВлево(self): + return self._переместить_робота(ТипНаправленийХимНапр.ВлевоX) + + def СдвинутьВправо(self): + return self._переместить_робота(ТипНаправленийХимНапр.ВправоX) + + def Подняться(self): + return self._переместить_робота(ТипНаправленийХимНапр.ДиагXB) + + def Спуститься(self): + return self._переместить_робота(ТипНаправленийХимНапр.ДиагXH) + + def Реактив(self): + if self.текущая_ячейка is None: + return + if self.текущая_ячейка.тип_ячейки == ТипЯчеекХимии.Реактив: + self.текущая_ячейка.тип_ячейки = ТипЯчеекХимии.Обработано + self.обработано_реактивов += 1 + + def Пусто(self): + if self.текущая_ячейка is None: + return + if self.текущая_ячейка.тип_ячейки == ТипЯчеекХимии.Пусто: + self.текущая_ячейка.тип_ячейки = ТипЯчеекХимии.Реактив + self.обработано_пустот += 1 + + def обработать_текущую_ячейку(self): + if self.текущая_ячейка is None: + return + + if self.текущая_ячейка.тип_ячейки == ТипЯчеекХимии.Реактив: + self.Реактив() + elif self.текущая_ячейка.тип_ячейки == ТипЯчеекХимии.Пусто: + self.Пусто() + self.Реактив() + + def проверить_цель(self): + if self.текущая_ячейка is None: + return False + + for row in self.лабиринт.ячейки: + for cell in row: + if cell.тип_ячейки in [ТипЯчеекХимии.Реактив, ТипЯчеекХимии.Пусто]: + return False + + return self.текущая_ячейка.тип_ячейки == ТипЯчеекХимии.Финиш + + +def init_session_state(): + if 'robot' not in st.session_state: + maze = ЛабиринтРоботХимик(ширина=5, длина=5) + st.session_state.robot = РоботХимик(maze) + st.session_state.iterator = None + st.session_state.auto_mode = False + st.session_state.logs = [] + + +def get_cell_color(cell_type): + colors = { + "Пусто": "white", + "Реактив": "#90EE90", + "Обработано": "#87CEEB", + "Контейнер": "#D3D3D3", + "Опасно": "#FFB6C1", + "Барьер": "#696969", + "Финиш": "#FFD700", + } + return colors.get(cell_type, "white") + + +def render_grid(): + robot = st.session_state.robot + maze = robot.лабиринт + + st.markdown("### Лабиринт 5×5") + + for y in range(maze.длина - 1, -1, -1): + cols = st.columns(maze.ширина) + + for x in range(maze.ширина): + cell = maze.ячейки[y][x] + cell_type = cell.тип_ячейки.value if cell.тип_ячейки else "Пусто" + color = get_cell_color(cell_type) + + with cols[x]: + border_color = "#4A86E8" if cell.ячейка_робота else "#333333" + border_width = "3px" if cell.ячейка_робота else "1px" + + st.markdown(f""" +
+
+ {x},{y} +
+
+ {cell_type[:3]} +
+ """, unsafe_allow_html=True) + + if cell.ячейка_робота: + st.markdown( + '
R
', + unsafe_allow_html=True) + + st.markdown('
', unsafe_allow_html=True) + + +def move_robot(direction): + robot = st.session_state.robot + result = None + + if direction == "Вперёд": + result = robot.ДвигВперёд() + elif direction == "Назад": + result = robot.Отодвинуть() + elif direction == "Влево": + result = robot.СдвинутьВлево() + elif direction == "Вправо": + result = robot.СдвинутьВправо() + elif direction == "Подняться": + result = robot.Подняться() + elif direction == "Спуститься": + result = robot.Спуститься() + + if result: + st.session_state.logs.append(f"Движение {direction} → ({result.x},{result.y})") + else: + st.session_state.logs.append(f"Движение {direction}: невозможно!") + + +def process_cell(): + robot = st.session_state.robot + old_type = robot.текущая_ячейка.тип_ячейки.value if robot.текущая_ячейка.тип_ячейки else "None" + robot.обработать_текущую_ячейку() + new_type = robot.текущая_ячейка.тип_ячейки.value if robot.текущая_ячейка.тип_ячейки else "None" + + if old_type != new_type: + st.session_state.logs.append(f"Обработка: {old_type} → {new_type}") + else: + st.session_state.logs.append(f"Ячейка не требует обработки: {old_type}") + + +def start_snake(): + robot = st.session_state.robot + st.session_state.iterator = robot.лабиринт.ПолучитьИтератор() + st.session_state.auto_mode = True + st.session_state.logs.append("Запущен автоматический режим (змейка)") + + +def auto_step(): + if not st.session_state.auto_mode: + return + + try: + robot = st.session_state.robot + next_cell = next(st.session_state.iterator) + + if (next_cell.x == robot.текущая_ячейка.x and + next_cell.y == robot.текущая_ячейка.y): + next_cell = next(st.session_state.iterator) + + dx = next_cell.x - robot.текущая_ячейка.x + dy = next_cell.y - robot.текущая_ячейка.y + + result = None + if dx == 1 and dy == 0: + result = robot.СдвинутьВправо() + elif dx == -1 and dy == 0: + result = robot.СдвинутьВлево() + elif dx == 0 and dy == 1: + result = robot.ДвигВперёд() + elif dx == 0 and dy == -1: + result = robot.Отодвинуть() + elif dx == -1 and dy == 1: + result = robot.Подняться() + elif dx == 1 and dy == -1: + result = robot.Спуститься() + + if result: + robot.обработать_текущую_ячейку() + st.session_state.logs.append(f"Авто-шаг → ({result.x},{result.y})") + else: + st.session_state.logs.append("Авто-шаг: движение невозможно!") + st.session_state.auto_mode = False + + except StopIteration: + st.session_state.logs.append("Автоматический режим завершен: пройдены все ячейки") + st.session_state.auto_mode = False + + +def check_goal(): + robot = st.session_state.robot + if robot.проверить_цель(): + st.success("🎉 Цель достигнута!") + st.session_state.logs.append("Цель достигнута!") + else: + remaining = [] + for row in robot.лабиринт.ячейки: + for cell in row: + if cell.тип_ячейки == ТипЯчеекХимии.Реактив: + remaining.append(f"Реактив в ({cell.x},{cell.y})") + elif cell.тип_ячейки == ТипЯчеекХимии.Пусто: + remaining.append(f"Пусто в ({cell.x},{cell.y})") + + if remaining: + st.warning(f"Цель не достигнута! Не обработано: {len(remaining)} ячеек") + st.session_state.logs.append(f"Цель не достигнута! Не обработано: {len(remaining)} ячеек") + elif robot.текущая_ячейка.тип_ячейки != ТипЯчеекХимии.Финиш: + st.warning(f"Робот не на финише! Позиция: ({robot.текущая_ячейка.x},{robot.текущая_ячейка.y})") + st.session_state.logs.append( + f"Робот не на финише! Позиция: ({robot.текущая_ячейка.x},{robot.текущая_ячейка.y})") + else: + st.info("Все условия выполнены, кроме нахождения на финише") + st.session_state.logs.append("Все условия выполнены, кроме нахождения на финише") + + +def restart_game(): + st.session_state.auto_mode = False + st.session_state.iterator = None + st.session_state.logs = [] + maze = ЛабиринтРоботХимик(ширина=5, длина=5) + st.session_state.robot = РоботХимик(maze) + st.session_state.logs.append("НОВАЯ ИГРА: случайный лабиринт 5x5") + st.session_state.logs.append("Робот начинает в позиции (0,0)") + + +def render_controls(): + st.markdown("### Управление движением") + + col1, col2, col3 = st.columns(3) + + with col2: + if st.button("⬆", key="up", use_container_width=True, help="ДвигВперёд"): + move_robot("Вперёд") + st.rerun() + + with col1: + if st.button("⬅", key="left", use_container_width=True, help="СдвинутьВлево"): + move_robot("Влево") + st.rerun() + + with col2: + if st.button("⚡", key="process", use_container_width=True, type="primary", help="Обработать ячейку"): + process_cell() + st.rerun() + + with col3: + if st.button("➡", key="right", use_container_width=True, help="СдвинутьВправо"): + move_robot("Вправо") + st.rerun() + + with col2: + if st.button("⬇", key="down", use_container_width=True, help="Отодвинуть"): + move_robot("Назад") + st.rerun() + + st.markdown("") + col1, col2 = st.columns(2) + + with col1: + if st.button("↖ Подняться", key="up_left", use_container_width=True): + move_robot("Подняться") + st.rerun() + + with col2: + if st.button("↘ Спуститься", key="down_right", use_container_width=True): + move_robot("Спуститься") + st.rerun() + + st.markdown("### Действия") + col1, col2 = st.columns(2) + + with col1: + if st.button("Реактив", key="react", use_container_width=True, help="Реактив → Обработано"): + st.session_state.robot.Реактив() + st.session_state.logs.append(f"Реактив → Обработано") + st.rerun() + + with col2: + if st.button("Пусто", key="empty", use_container_width=True, help="Пусто → Реактив"): + st.session_state.robot.Пусто() + st.session_state.logs.append(f"Пусто → Реактив") + st.rerun() + + st.markdown("### Авто-режим") + col1, col2, col3 = st.columns(3) + + with col1: + if st.button("▶", key="start_auto", use_container_width=True, help="Запустить змейку"): + start_snake() + st.rerun() + + with col2: + if st.button("⏭", key="next_auto", use_container_width=True, + disabled=not st.session_state.auto_mode, help="Следующий шаг"): + auto_step() + st.rerun() + + with col3: + if st.button("⏹", key="stop_auto", use_container_width=True, + disabled=not st.session_state.auto_mode, help="Остановить"): + st.session_state.auto_mode = False + st.session_state.logs.append("Автоматический режим остановлен") + st.rerun() + + st.markdown("") + col1, col2 = st.columns(2) + + with col1: + if st.button("🔍 Проверить", key="check", use_container_width=True): + check_goal() + st.rerun() + + with col2: + if st.button("🔄 Новая", key="restart", use_container_width=True): + restart_game() + st.rerun() + + +def render_logs(): + st.markdown("### Лог действий") + + with st.container(height=300): + if st.session_state.logs: + for log in reversed(st.session_state.logs[-15:]): + st.text(log) + else: + st.text("Лог пуст") + + +def render_legend(): + with st.expander("Легенда цветов"): + legend_items = [ + ("Пусто", "white", "Пустая ячейка"), + ("Реактив", "#90EE90", "Химический реактив"), + ("Обработано", "#87CEEB", "Обработанный реактив"), + ("Контейнер", "#D3D3D3", "Контейнер"), + ("Опасно", "#FFB6C1", "Опасная зона"), + ("Барьер", "#696969", "Препятствие"), + ("Финиш", "#FFD700", "Финишная ячейка"), + ] + + for label, color, desc in legend_items: + col1, col2 = st.columns([1, 4]) + with col1: + st.markdown( + f'
', + unsafe_allow_html=True) + with col2: + st.markdown(f"**{label}**
{desc}", unsafe_allow_html=True) + + +def create_mapping_yaml(): + mapping = { + 'классы': [ + { + 'Класс диаграммы': 'РоботХимик', + 'свойства': [ + {'лабиринт': 'лабиринт'} + ], + 'методы': [ + {'ДвигВперёд': 'ДвигВперёд'}, + {'Отодвинуть': 'Отодвинуть'}, + {'СдвинутьВлево': 'СдвинутьВлево'}, + {'СдвинутьВправо': 'СдвинутьВправо'}, + {'Подняться': 'Подняться'}, + {'Спуститься': 'Спуститься'}, + {'Реактив': 'Реактив'}, + {'Пусто': 'Пусто'} + ] + }, + { + 'Класс диаграммы': 'ЛабиринтРоботХимик', + 'свойства': [ + {'ширина': 'ширина'}, + {'длина': 'длина'}, + {'ячейки': 'ячейки'} + ], + 'методы': [ + {'ПолучитьСоседнююЯчейку': 'ПолучитьСоседнююЯчейку'}, + {'ПолучитьИтератор': 'ПолучитьИтератор'}, + {'ИнициализироватьЛабиринт': 'ИнициализироватьЛабиринт'} + ] + }, + { + 'Класс диаграммы': 'ЯчейкаРоботХимик', + 'свойства': [ + {'ячейка_робота': 'ячейка_робота'}, + {'тип_ячейки': 'тип_ячейки'} + ], + 'методы': [] + } + ], + 'перечисления': [ + { + 'ПеречислениеНаДиаграмме': 'ТипНаправленийХимНапр', + 'опции': [ + {'ВперёдX': 'ВперёдX'}, + {'НазадX': 'НазадX'}, + {'ВлевоX': 'ВлевоX'}, + {'ВправоX': 'ВправоX'}, + {'ДиагXB': 'ДиагXB'}, + {'ДиагXH': 'ДиагXH'} + ] + }, + { + 'ПеречислениеНаДиаграмме': 'ТипЯчеекХимии', + 'опции': [ + {'Пусто': 'Пусто'}, + {'Реактив': 'Реактив'}, + {'Обработано': 'Обработано'}, + {'Контейнер': 'Контейнер'}, + {'Опасно': 'Опасно'}, + {'Барьер': 'Барьер'}, + {'Финиш': 'Финиш'} + ] + } + ] + } + + with open('mapping.yaml', 'w', encoding='utf-8') as f: + yaml.dump(mapping, f, allow_unicode=True, default_flow_style=False) + + +def main(): + st.set_page_config(page_title="Робот-Химик", layout="wide") + + st.markdown(""" + + """, unsafe_allow_html=True) + + init_session_state() + + st.markdown('
🤖 Робот-Химик
', unsafe_allow_html=True) + + if st.session_state.auto_mode: + st.info("Автоматический режим активен") + + # Основной макет + col1, col2 = st.columns([2, 1]) + + with col1: + render_grid() + + with col2: + render_controls() + render_logs() + render_legend() + + with st.expander("Условие задачи"): + st.markdown(""" + **Цель:** + 1. Обработать все реактивы и пустые ячейки + 2. Закончить на финише + + **Действия:** + - Реактив → Обработано + - Пусто → Реактив → Обработано + + **Ограничения:** + - Нельзя ходить на Опасно и Барьер + + **Координаты:** + - Y - север (вверх) + - X - восток (вправо) + - Начало: (0,0) - нижний левый угол + """) + + +if __name__ == "__main__": + create_mapping_yaml() + main() diff --git a/README.md b/README.md index 84f92f1..82dba7f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,42 @@ # Разработка пользовательского интерфейса -Хранилище 1го и 2го этапа лабораторной работы по дисциплине -Структура: +Лабораторная работа по дисциплине «Разработка пользовательского интерфейса». СПбПУ, 2025–2026. + +## Этапы + +| Этап | Платформа | Стек | +|------|-----------|------| +| 1 | Десктопное приложение | Python, Tkinter, PyYAML | +| 2 | Веб-приложение | Python, Streamlit, PyYAML | + +## Задание + +Реализация симуляции «Робот-химик» — программа управляет роботом в лабиринте химической лаборатории. Робот собирает реактивы, обходит препятствия и находит путь к цели. + +Входные данные — YAML-маппинг классов и перечислений (UML-диаграмма), на основе которого строится доменная модель. + +## Структура + +``` +5030102_30201/ # группа 30201 +└── ФИО_студента/ + ├── desktop.py # этап 1 (Tkinter) + ├── web.py # этап 2 (Streamlit) + └── видео.mp4 # демонстрация работы + +5030102_30202/ # группа 30202 +└── ФИО_студента/ + └── ... ``` -─> Папка 5030102_30201 -├─> ФИО студента -... -└─> ФИО студента - ├─> файл .py 1 этапа - ├─> файл .py 2 этапа - └─> Видео -─> Папка 5030102_30202 -└─> аналогично, как и в первой папке + +## Запуск + +```bash +# Этап 1 (десктоп) +pip install pyyaml +python desktop.py + +# Этап 2 (веб) +pip install streamlit pyyaml +streamlit run web.py ```