diff --git "a/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step1/README.md" "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step1/README.md" new file mode 100644 index 0000000..d07cd84 --- /dev/null +++ "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step1/README.md" @@ -0,0 +1 @@ +### Для запуска необходими иметь установленный tkinter (через pip не установить) \ No newline at end of file diff --git "a/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step1/step1.py" "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step1/step1.py" new file mode 100644 index 0000000..a9f4f02 --- /dev/null +++ "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step1/step1.py" @@ -0,0 +1,466 @@ +import tkinter as tk +import time +from tkinter import messagebox +from enum import Enum, auto +from typing import Optional, Iterator + +class WorkshopCellType(Enum): + Floor = auto() + Detail = auto() + Repaired = auto() + Tool = auto() + Obstacle = auto() + Oil = auto() + Finish = auto() + +class BaseDirection(Enum): + N = auto() + S = auto() + W = auto() + E = auto() + NW = auto() + SE = auto() + +class WorkDirection(Enum): + Forward = auto() + Backward = auto() + Left = auto() + Right = auto() + DiagUp = auto() + DiagDown = auto() + +class SideDirection: + def __init__(self, side: BaseDirection, direction: WorkDirection): + self.side = side + self.direction = direction + +SIDE_DIRECTION_1 = SideDirection(BaseDirection.N, WorkDirection.Forward) +SIDE_DIRECTION_2 = SideDirection(BaseDirection.S, WorkDirection.Backward) +SIDE_DIRECTION_3 = SideDirection(BaseDirection.W, WorkDirection.Left) +SIDE_DIRECTION_4 = SideDirection(BaseDirection.E, WorkDirection.Right) +SIDE_DIRECTION_5 = SideDirection(BaseDirection.NW, WorkDirection.DiagUp) +SIDE_DIRECTION_6 = SideDirection(BaseDirection.SE, WorkDirection.DiagDown) + +class MethodDirection: + def __init__(self, method_id: str, direction_value: int): + self.method_id = method_id + self.direction_value = direction_value + +METHOD_DIRECTION_1 = MethodDirection("ЕхатьКРаботе", WorkDirection.Forward.value) +METHOD_DIRECTION_2 = MethodDirection("Отойти", WorkDirection.Backward.value) +METHOD_DIRECTION_3 = MethodDirection("СместитьВлево", WorkDirection.Left.value) +METHOD_DIRECTION_4 = MethodDirection("СместитьВправо", WorkDirection.Right.value) +METHOD_DIRECTION_5 = MethodDirection("ПоднятьсяДиаг", WorkDirection.DiagUp.value) +METHOD_DIRECTION_6 = MethodDirection("СпуститьсяДиаг", WorkDirection.DiagDown.value) + +class WorkshopCell: + def __init__(self, cell_type: WorkshopCellType, has_robot: bool = False, x: int = 0, y: int = 0): + self.cell_type = cell_type + self.has_robot = has_robot + self.x = x + self.y = y + +class Workshop: + def __init__(self, width: int, height: int, + cells: Optional[list[list[WorkshopCell]]] = None): + self.width = width + self.height = height + if cells is None: + self.cells = [[WorkshopCell(WorkshopCellType.Floor, False, x, y) for y in range(self.height)] for x in range(self.width)] + else: + self.cells = cells + + def initialize_workshop(self, cell_type: WorkshopCellType): + for x in range(self.width): + for y in range(self.height): + cell = self.cells[x][y] + cell.x = x + cell.y = y + cell.cell_type = cell_type + + def get_adjacent_cell(self, current_cell: WorkshopCell, direction_value: int) -> Optional[WorkshopCell]: + direction = WorkDirection(direction_value) + + dx, dy = 0, 0 + if direction == WorkDirection.Forward: + dy = 1 + elif direction == WorkDirection.Backward: + dy = -1 + elif direction == WorkDirection.Left: + dx = -1 + elif direction == WorkDirection.Right: + dx = 1 + elif direction == WorkDirection.DiagUp: + dx = -1 + dy = 1 + elif direction == WorkDirection.DiagDown: + dx = 1 + dy = -1 + + nx = current_cell.x + dx + ny = current_cell.y + dy + + if 0 <= nx < self.width and 0 <= ny < self.height: + return self.cells[nx][ny] + return None + + def get_snake_iterator(self) -> Iterator[WorkshopCell]: + return SnakeIterator(self) + +class SnakeIterator: + def __init__(self, workshop: Workshop): + self.width = workshop.width + self.height = workshop.height + self.workshop = workshop + self.x = 0 + self.y = 0 + self.direction = "right" + self.first = True + + def __iter__(self): + return self + + def __next__(self) -> WorkshopCell: + if self.first: + self.first = False + return self.workshop.cells[self.x][self.y] + if self.direction == "right": + if self.x + 1 < self.width: + self.x += 1 + else: + if self.y + 1 >= self.height: + raise StopIteration + self.y += 1 + self.direction = "left" + else: # "left" + if self.x - 1 >= 0: + self.x -= 1 + else: + if self.y + 1 >= self.height: + raise StopIteration + self.y += 1 + self.direction = "right" + return self.workshop.cells[self.x][self.y] + +class RobotMechanic: + def __init__(self, workshop: Workshop): + self.workshop = workshop + self.current_cell = self._find_robot_cell() + + def _find_robot_cell(self) -> WorkshopCell: + for x in range(self.workshop.width): + for y in range(self.workshop.height): + cell = self.workshop.cells[x][y] + if cell.has_robot: + return cell + # Если робота нет, ставим в левый нижний угол (0,0) в координатах модели + start_cell = self.workshop.cells[0][0] + start_cell.has_robot = True + return start_cell + + def move_forward(self) -> Optional[WorkshopCell]: + return self._move_by_direction_value(METHOD_DIRECTION_1.direction_value) + + def move_backward(self) -> Optional[WorkshopCell]: + return self._move_by_direction_value(METHOD_DIRECTION_2.direction_value) + + def move_left(self) -> Optional[WorkshopCell]: + return self._move_by_direction_value(METHOD_DIRECTION_3.direction_value) + + def move_right(self) -> Optional[WorkshopCell]: + return self._move_by_direction_value(METHOD_DIRECTION_4.direction_value) + + def move_diag_up(self) -> Optional[WorkshopCell]: + return self._move_by_direction_value(METHOD_DIRECTION_5.direction_value) + + def move_diag_down(self) -> Optional[WorkshopCell]: + return self._move_by_direction_value(METHOD_DIRECTION_6.direction_value) + + def repair_detail(self): + if self.current_cell.cell_type == WorkshopCellType.Detail: + self.current_cell.cell_type = WorkshopCellType.Repaired + + def _move_by_direction_value(self, direction_value: int) -> Optional[WorkshopCell]: + next_cell = self.workshop.get_adjacent_cell(self.current_cell, direction_value) + if next_cell is None: + return None + if next_cell.cell_type in (WorkshopCellType.Obstacle, WorkshopCellType.Oil): + return None + + self.current_cell.has_robot = False + next_cell.has_robot = True + self.current_cell = next_cell + return next_cell + + def execute_task(self): + iterator = self.workshop.get_snake_iterator() + for cell in iterator: + self.current_cell.has_robot = False + cell.has_robot = True + self.current_cell = cell + + if cell.cell_type == WorkshopCellType.Detail: + self.repair_detail() + + if cell.cell_type == WorkshopCellType.Finish: + return + + +class WorkshopGUI: + CELL_SIZE = 50 + COLORS = { + WorkshopCellType.Floor: "#e0e0e0", + WorkshopCellType.Detail: "#ffdd44", + WorkshopCellType.Repaired: "#4caf50", + WorkshopCellType.Tool: "#2196f3", + WorkshopCellType.Obstacle: "#757575", + WorkshopCellType.Oil: "#8d6e63", + WorkshopCellType.Finish: "#ff9800" + } + ROBOT_COLOR = "#f44336" + GRID_COLOR = "#616161" + + def __init__(self, workshop: Workshop, robot: RobotMechanic): + self.workshop = workshop + self.robot = robot + self.root = tk.Tk() + self.root.title("Робот-механик | Лабораторная работа") + self.root.geometry("800x600") + self.root.resizable(False, False) + + main_frame = tk.Frame(self.root) + main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + + self.control_frame = tk.Frame(main_frame, padx=5, pady=5) + self.control_frame.pack(fill=tk.X) + + content_frame = tk.Frame(main_frame) + content_frame.pack(fill=tk.BOTH, expand=True) + + canvas_width = workshop.width * self.CELL_SIZE + 20 + canvas_height = workshop.height * self.CELL_SIZE + 20 + self.canvas_frame = tk.Frame(content_frame, padx=5, pady=5) + self.canvas_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=False) + + self.canvas = tk.Canvas( + self.canvas_frame, + width=canvas_width, + height=canvas_height, + bg="#f5f5f5", + highlightthickness=1, + highlightbackground="#bdbdbd" + ) + self.canvas.pack() + + self.legend_frame = tk.Frame(content_frame, padx=5, pady=5, bg="#ffffff", relief=tk.GROOVE, bd=1) + self.legend_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(10, 0)) + + self.status_frame = tk.Frame(main_frame, padx=5, pady=5) + self.status_frame.pack(fill=tk.X, side=tk.BOTTOM) + + buttons = [ + ("Вперед", self.move_forward), + ("Назад", self.move_backward), + ("Влево", self.move_left), + ("Вправо", self.move_right), + ("Диаг. вверх", self.move_diag_up), + ("Диаг. вниз", self.move_diag_down), + ("Починить", self.repair), + ("Выполнить задачу", self.execute_task) + ] + + for text, command in buttons: + btn = tk.Button( + self.control_frame, + text=text, + command=command, + font=("Arial", 9, "bold"), + bg="#2196f3", + fg="white", + padx=8, + pady=3 + ) + btn.pack(side=tk.LEFT, padx=3, pady=3) + + self.create_legend() + + self.status_var = tk.StringVar() + self.status_var.set("Готов к работе | Координаты робота: (0, 0)") + self.status_label = tk.Label( + self.status_frame, + textvariable=self.status_var, + font=("Arial", 10), + fg="#212121", + anchor=tk.W + ) + self.status_label.pack(fill=tk.X, padx=5, pady=2) + + self.draw_workshop() + + self.root.protocol("WM_DELETE_WINDOW", self.on_closing) + + def create_legend(self): + """Создает компактную легенду справа от поля""" + tk.Label(self.legend_frame, text="Легенда:", font=("Arial", 11, "bold"), bg="#ffffff").pack(anchor=tk.W, pady=(0, 5)) + + legend_items = [ + (WorkshopCellType.Floor, "Пол", "#e0e0e0"), + (WorkshopCellType.Detail, "Деталь (D)", "#ffdd44"), + (WorkshopCellType.Repaired, "Отремонтировано", "#4caf50"), + (WorkshopCellType.Tool, "Инструмент", "#2196f3"), + (WorkshopCellType.Obstacle, "Препятствие", "#757575"), + (WorkshopCellType.Oil, "Масляное пятно", "#8d6e63"), + (WorkshopCellType.Finish, "Финиш (F)", "#ff9800"), + (None, "Робот (R)", "#f44336") + ] + + for cell_type, name, color in legend_items: + item_frame = tk.Frame(self.legend_frame, bg="#ffffff") + item_frame.pack(fill=tk.X, padx=2, pady=2) + + color_canvas = tk.Canvas(item_frame, width=20, height=20, bg=color, highlightthickness=1, highlightbackground="#9e9e9e") + color_canvas.pack(side=tk.LEFT, padx=5) + + if cell_type is None: + color_canvas.create_text(10, 10, text="R", font=("Arial", 8, "bold"), fill="white") + elif cell_type == WorkshopCellType.Detail: + color_canvas.create_text(10, 10, text="D", font=("Arial", 8, "bold")) + elif cell_type == WorkshopCellType.Finish: + color_canvas.create_text(10, 10, text="F", font=("Arial", 8, "bold")) + elif cell_type == WorkshopCellType.Tool: + color_canvas.create_text(10, 10, text="T", font=("Arial", 8, "bold")) + + tk.Label(item_frame, text=name, font=("Arial", 9), bg="#ffffff").pack(side=tk.LEFT, padx=5) + + def draw_workshop(self): + self.canvas.delete("all") + offset = 10 + + for x in range(self.workshop.width + 1): + self.canvas.create_line( + offset + x * self.CELL_SIZE, offset, + offset + x * self.CELL_SIZE, offset + self.workshop.height * self.CELL_SIZE, + fill=self.GRID_COLOR, width=1 + ) + + for y in range(self.workshop.height + 1): + self.canvas.create_line( + offset, offset + y * self.CELL_SIZE, + offset + self.workshop.width * self.CELL_SIZE, offset + y * self.CELL_SIZE, + fill=self.GRID_COLOR, width=1 + ) + + for x in range(self.workshop.width): + for y in range(self.workshop.height): + cell = self.workshop.cells[x][y] + model_y = self.workshop.height - 1 - y + + x1 = offset + x * self.CELL_SIZE + y1 = offset + model_y * self.CELL_SIZE + x2 = x1 + self.CELL_SIZE + y2 = y1 + self.CELL_SIZE + + fill_color = self.COLORS.get(cell.cell_type, "#ffffff") + self.canvas.create_rectangle( + x1, y1, x2, y2, + fill=fill_color, + outline="#9e9e9e", + width=1 + ) + + if cell.cell_type in [WorkshopCellType.Detail, WorkshopCellType.Finish]: + text = "D" if cell.cell_type == WorkshopCellType.Detail else "F" + self.canvas.create_text( + (x1 + x2) // 2, (y1 + y2) // 2, + text=text, + font=("Arial", 14, "bold"), + fill="black" + ) + + if cell.has_robot: + self.canvas.create_oval( + x1 + 8, y1 + 8, x2 - 8, y2 - 8, + fill=self.ROBOT_COLOR, + outline="#d32f2f", + width=2 + ) + self.canvas.create_text( + (x1 + x2) // 2, (y1 + y2) // 2, + text="R", + font=("Arial", 12, "bold"), + fill="white" + ) + + self.update_status() + + def update_status(self): + coords = f"({self.robot.current_cell.x}, {self.robot.current_cell.y})" + cell_type = self.robot.current_cell.cell_type.name + status = f"Текущая ячейка: {cell_type} | Координаты робота: {coords}" + self.status_var.set(status) + + def move_forward(self): + self._execute_move(self.robot.move_forward) + + def move_backward(self): + self._execute_move(self.robot.move_backward) + + def move_left(self): + self._execute_move(self.robot.move_left) + + def move_right(self): + self._execute_move(self.robot.move_right) + + def move_diag_up(self): + self._execute_move(self.robot.move_diag_up) + + def move_diag_down(self): + self._execute_move(self.robot.move_diag_down) + + def repair(self): + prev_type = self.robot.current_cell.cell_type + self.robot.repair_detail() + self.draw_workshop() + + if prev_type == WorkshopCellType.Detail: + messagebox.showinfo("Успех", "Деталь успешно отремонтирована!") + else: + messagebox.showinfo("Информация", "Нечего ремонтировать в этой ячейке") + + def execute_task(self): + self.robot.execute_task() + self.draw_workshop() + messagebox.showinfo("Задание выполнено", "Робот завершил обход по змейке!") + + def _execute_move(self, move_func): + result = move_func() + if result is None: + messagebox.showwarning("Ошибка движения", "Невозможно переместиться в этом направлении!\nПроверьте наличие препятствий или границы поля") + else: + self.draw_workshop() + + def on_closing(self): + if messagebox.askokcancel("Выход", "Завершить работу с лабораторной?"): + self.root.destroy() + + def run(self): + self.root.mainloop() + +if __name__ == "__main__": + workshop = Workshop(7, 6) + + workshop.cells[0][0].has_robot = True + workshop.cells[1][0].cell_type = WorkshopCellType.Detail + workshop.cells[2][1].cell_type = WorkshopCellType.Detail + workshop.cells[4][0].cell_type = WorkshopCellType.Obstacle + workshop.cells[3][2].cell_type = WorkshopCellType.Oil + workshop.cells[5][1].cell_type = WorkshopCellType.Tool + workshop.cells[6][5].cell_type = WorkshopCellType.Finish + + workshop.cells[3][4].cell_type = WorkshopCellType.Detail + workshop.cells[5][3].cell_type = WorkshopCellType.Detail + + robot = RobotMechanic(workshop) + + app = WorkshopGUI(workshop, robot) + app.run() \ No newline at end of file diff --git "a/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/README.md" "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/README.md" new file mode 100644 index 0000000..0caa7c2 --- /dev/null +++ "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/README.md" @@ -0,0 +1,4 @@ +# Для запуска: +1. `pip install fastapi uvicorn jinja2 python-multipart` +2. `py ./step2.py` +3. Откройте в браузере `http://localhost:8000` \ No newline at end of file diff --git "a/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/main.py" "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/main.py" new file mode 100644 index 0000000..ea0c46d --- /dev/null +++ "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/main.py" @@ -0,0 +1,404 @@ +import os +from fastapi import FastAPI, Request +from fastapi.responses import HTMLResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates +from pydantic import BaseModel + +from enum import Enum, auto +from typing import Optional, Iterator, List + +class WorkshopCellType(Enum): + Floor = auto() + Detail = auto() + Repaired = auto() + Tool = auto() + Obstacle = auto() + Oil = auto() + Finish = auto() + +class BaseDirection(Enum): + N = auto() + S = auto() + W = auto() + E = auto() + NW = auto() + SE = auto() + +class WorkDirection(Enum): + Forward = auto() + Backward = auto() + Left = auto() + Right = auto() + DiagUp = auto() + DiagDown = auto() + +class SideDirection: + def __init__(self, side: BaseDirection, direction: WorkDirection): + self.side = side + self.direction = direction + +SIDE_DIRECTION_1 = SideDirection(BaseDirection.N, WorkDirection.Forward) +SIDE_DIRECTION_2 = SideDirection(BaseDirection.S, WorkDirection.Backward) +SIDE_DIRECTION_3 = SideDirection(BaseDirection.W, WorkDirection.Left) +SIDE_DIRECTION_4 = SideDirection(BaseDirection.E, WorkDirection.Right) +SIDE_DIRECTION_5 = SideDirection(BaseDirection.NW, WorkDirection.DiagUp) +SIDE_DIRECTION_6 = SideDirection(BaseDirection.SE, WorkDirection.DiagDown) + +class MethodDirection: + def __init__(self, method_id: str, direction_value: int): + self.method_id = method_id + self.direction_value = direction_value + +METHOD_DIRECTION_1 = MethodDirection("ЕхатьКРаботе", WorkDirection.Forward.value) +METHOD_DIRECTION_2 = MethodDirection("Отойти", WorkDirection.Backward.value) +METHOD_DIRECTION_3 = MethodDirection("СместитьВлево", WorkDirection.Left.value) +METHOD_DIRECTION_4 = MethodDirection("СместитьВправо", WorkDirection.Right.value) +METHOD_DIRECTION_5 = MethodDirection("ПоднятьсяДиаг", WorkDirection.DiagUp.value) +METHOD_DIRECTION_6 = MethodDirection("СпуститьсяДиаг", WorkDirection.DiagDown.value) + +class WorkshopCell: + def __init__(self, cell_type: WorkshopCellType, has_robot: bool = False, x: int = 0, y: int = 0): + self.cell_type = cell_type + self.has_robot = has_robot + self.x = x + self.y = y + +class Workshop: + def __init__(self, width: int, height: int, + cells: Optional[List[List[WorkshopCell]]] = None): + self.width = width + self.height = height + if cells is None: + self.cells = [[WorkshopCell(WorkshopCellType.Floor, False, x, y) for y in range(self.height)] for x in range(self.width)] + else: + self.cells = cells + + def initialize_workshop(self, cell_type: WorkshopCellType): + for x in range(self.width): + for y in range(self.height): + cell = self.cells[x][y] + cell.x = x + cell.y = y + cell.cell_type = cell_type + + def get_adjacent_cell(self, current_cell: WorkshopCell, direction_value: int) -> Optional[WorkshopCell]: + direction = WorkDirection(direction_value) + + dx, dy = 0, 0 + if direction == WorkDirection.Forward: + dy = 1 + elif direction == WorkDirection.Backward: + dy = -1 + elif direction == WorkDirection.Left: + dx = -1 + elif direction == WorkDirection.Right: + dx = 1 + elif direction == WorkDirection.DiagUp: + dx = -1 + dy = 1 + elif direction == WorkDirection.DiagDown: + dx = 1 + dy = -1 + + nx = current_cell.x + dx + ny = current_cell.y + dy + + if 0 <= nx < self.width and 0 <= ny < self.height: + return self.cells[nx][ny] + return None + + def get_snake_iterator(self) -> Iterator[WorkshopCell]: + return SnakeIterator(self) + +class SnakeIterator: + def __init__(self, workshop: Workshop): + self.width = workshop.width + self.height = workshop.height + self.workshop = workshop + self.x = 0 + self.y = 0 + self.direction = "right" + self.first = True + + def __iter__(self): + return self + + def __next__(self) -> WorkshopCell: + if self.first: + self.first = False + return self.workshop.cells[self.x][self.y] + if self.direction == "right": + if self.x + 1 < self.width: + self.x += 1 + else: + if self.y + 1 >= self.height: + raise StopIteration + self.y += 1 + self.direction = "left" + else: # "left" + if self.x - 1 >= 0: + self.x -= 1 + else: + if self.y + 1 >= self.height: + raise StopIteration + self.y += 1 + self.direction = "right" + return self.workshop.cells[self.x][self.y] + +class RobotMechanic: + def __init__(self, workshop: Workshop): + self.workshop = workshop + self.current_cell = self._find_robot_cell() + + def _find_robot_cell(self) -> WorkshopCell: + for x in range(self.workshop.width): + for y in range(self.workshop.height): + cell = self.workshop.cells[x][y] + if cell.has_robot: + return cell + start_cell = self.workshop.cells[0][0] + start_cell.has_robot = True + return start_cell + + def move_forward(self) -> Optional[WorkshopCell]: + return self._move_by_direction_value(METHOD_DIRECTION_1.direction_value) + + def move_backward(self) -> Optional[WorkshopCell]: + return self._move_by_direction_value(METHOD_DIRECTION_2.direction_value) + + def move_left(self) -> Optional[WorkshopCell]: + return self._move_by_direction_value(METHOD_DIRECTION_3.direction_value) + + def move_right(self) -> Optional[WorkshopCell]: + return self._move_by_direction_value(METHOD_DIRECTION_4.direction_value) + + def move_diag_up(self) -> Optional[WorkshopCell]: + return self._move_by_direction_value(METHOD_DIRECTION_5.direction_value) + + def move_diag_down(self) -> Optional[WorkshopCell]: + return self._move_by_direction_value(METHOD_DIRECTION_6.direction_value) + + def repair_detail(self): + if self.current_cell.cell_type == WorkshopCellType.Detail: + self.current_cell.cell_type = WorkshopCellType.Repaired + + def _move_by_direction_value(self, direction_value: int) -> Optional[WorkshopCell]: + next_cell = self.workshop.get_adjacent_cell(self.current_cell, direction_value) + if next_cell is None: + return None + if next_cell.cell_type in (WorkshopCellType.Obstacle, WorkshopCellType.Oil): + return None + + self.current_cell.has_robot = False + next_cell.has_robot = True + self.current_cell = next_cell + return next_cell + + def execute_task(self): + iterator = self.workshop.get_snake_iterator() + for cell in iterator: + self.current_cell.has_robot = False + cell.has_robot = True + self.current_cell = cell + + if cell.cell_type == WorkshopCellType.Detail: + self.repair_detail() + + if cell.cell_type == WorkshopCellType.Finish: + return + +app = FastAPI() + +templates = Jinja2Templates(directory="templates") +app.mount("/static", StaticFiles(directory="static"), name="static") + +workshop = Workshop(7, 6) +robot = RobotMechanic(workshop) + +def init_demo_field(): + global workshop, robot + workshop = Workshop(7, 6) + + workshop.cells[0][0].has_robot = True + workshop.cells[1][0].cell_type = WorkshopCellType.Detail + workshop.cells[2][1].cell_type = WorkshopCellType.Detail + workshop.cells[4][0].cell_type = WorkshopCellType.Obstacle + workshop.cells[3][2].cell_type = WorkshopCellType.Oil + workshop.cells[5][1].cell_type = WorkshopCellType.Tool + workshop.cells[6][5].cell_type = WorkshopCellType.Finish + + workshop.cells[3][4].cell_type = WorkshopCellType.Detail + workshop.cells[5][3].cell_type = WorkshopCellType.Detail + + robot = RobotMechanic(workshop) + +init_demo_field() + +def generate_svg_field(workshop: Workshop) -> str: + cell_size = 60 + width = workshop.width * cell_size + 20 + height = workshop.height * cell_size + 20 + offset = 10 + + svg_elements = [] + + for x in range(workshop.width + 1): + svg_elements.append( + f'' + ) + + for y in range(workshop.height + 1): + svg_elements.append( + f'' + ) + + colors = { + WorkshopCellType.Floor.name: "#e0e0e0", + WorkshopCellType.Detail.name: "#ffdd44", + WorkshopCellType.Repaired.name: "#4caf50", + WorkshopCellType.Tool.name: "#2196f3", + WorkshopCellType.Obstacle.name: "#757575", + WorkshopCellType.Oil.name: "#8d6e63", + WorkshopCellType.Finish.name: "#ff9800" + } + + for x in range(workshop.width): + for y in range(workshop.height): + cell = workshop.cells[x][y] + model_y = workshop.height - 1 - y + + x1 = offset + x * cell_size + y1 = offset + model_y * cell_size + x2 = x1 + cell_size + y2 = y1 + cell_size + + color = colors.get(cell.cell_type.name, "#ffffff") + + svg_elements.append( + f'' + ) + + if cell.cell_type in [WorkshopCellType.Detail, WorkshopCellType.Finish]: + text = "D" if cell.cell_type == WorkshopCellType.Detail else "F" + svg_elements.append( + f'{text}' + ) + + if cell.has_robot: + svg_elements.append( + f'' + ) + svg_elements.append( + f'R' + ) + + return ( + f'' + + "\n".join(svg_elements) + + '' + ) + +class MoveResponse(BaseModel): + success: bool + message: str + svg: str + robot_position: dict + cell_type: str + +@app.get("/", response_class=HTMLResponse) +async def get_lab_interface(request: Request): + svg = generate_svg_field(workshop) + return templates.TemplateResponse("index.html", { + "request": request, + "svg": svg, + "robot_position": {"x": robot.current_cell.x, "y": robot.current_cell.y}, + "cell_type": robot.current_cell.cell_type.name + }) + +@app.post("/move/{direction}") +async def move_robot(direction: str): + move_methods = { + "forward": robot.move_forward, + "backward": robot.move_backward, + "left": robot.move_left, + "right": robot.move_right, + "diag_up": robot.move_diag_up, + "diag_down": robot.move_diag_down + } + + if direction not in move_methods: + return MoveResponse( + success=False, + message="Неверное направление", + svg=generate_svg_field(workshop), + robot_position={"x": robot.current_cell.x, "y": robot.current_cell.y}, + cell_type=robot.current_cell.cell_type.name + ) + + result = move_methods[direction]() + success = result is not None + + message = "Успешное перемещение" if success else "Невозможно переместиться: препятствие или граница поля" + + return MoveResponse( + success=success, + message=message, + svg=generate_svg_field(workshop), + robot_position={"x": robot.current_cell.x, "y": robot.current_cell.y}, + cell_type=robot.current_cell.cell_type.name + ) + +@app.post("/repair") +async def repair_detail(): + prev_type = robot.current_cell.cell_type + robot.repair_detail() + + success = prev_type == WorkshopCellType.Detail + message = "Деталь успешно отремонтирована!" if success else "Нечего ремонтировать в этой ячейке" + + return MoveResponse( + success=success, + message=message, + svg=generate_svg_field(workshop), + robot_position={"x": robot.current_cell.x, "y": robot.current_cell.y}, + cell_type=robot.current_cell.cell_type.name + ) + +@app.post("/execute_task") +async def execute_full_task(): + robot.execute_task() + return MoveResponse( + success=True, + message="Робот успешно выполнил обход по змейке!", + svg=generate_svg_field(workshop), + robot_position={"x": robot.current_cell.x, "y": robot.current_cell.y}, + cell_type=robot.current_cell.cell_type.name + ) + +@app.post("/reset") +async def reset_field(): + init_demo_field() + svg = generate_svg_field(workshop) + return { + "success": True, + "message": "Поле успешно сброшено", + "svg": svg, + "robot_position": {"x": robot.current_cell.x, "y": robot.current_cell.y}, + "cell_type": robot.current_cell.cell_type.name + } + +if __name__ == "__main__": + import uvicorn + uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True) \ No newline at end of file diff --git "a/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/static/.gitkeep" "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/static/.gitkeep" new file mode 100644 index 0000000..e69de29 diff --git "a/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/static/css/style.css" "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/static/css/style.css" new file mode 100644 index 0000000..c4b8f57 --- /dev/null +++ "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/static/css/style.css" @@ -0,0 +1,65 @@ +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&display=swap'); + +body { + font-family: 'JetBrains Mono', monospace; + background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d); + background-size: 400% 400%; + animation: gradientBG 15s ease infinite; +} + +@keyframes gradientBG { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +.control-btn { + transition: all 0.3s ease; + transform: scale(1); +} + +.control-btn:hover { + transform: scale(1.05); + box-shadow: 0 10px 25px rgba(0,0,0,0.3); +} + +.control-btn:active { + transform: scale(0.95); +} + +.glow { + position: relative; +} + +.glow::after { + content: ''; + position: absolute; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + background: linear-gradient(45deg, #ff9a9e, #fad0c4, #a1c4fd, #c2e9fb); + z-index: -1; + border-radius: 12px; + animation: glowing 20s linear infinite; + opacity: 0.5; +} + +@keyframes glowing { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.notification { + animation: slideIn 0.5s forwards, fadeOut 0.5s 2.5s forwards; +} + +@keyframes slideIn { + from { transform: translateX(100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +} + +@keyframes fadeOut { + from { opacity: 1; } + to { opacity: 0; } +} \ No newline at end of file diff --git "a/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/static/js/main.js" "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/static/js/main.js" new file mode 100644 index 0000000..b395516 --- /dev/null +++ "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/static/js/main.js" @@ -0,0 +1,111 @@ +async function moveRobot(direction) { + showLoading(); + try { + const response = await fetch(`/move/${direction}`, { + method: 'POST' + }); + const data = await response.json(); + updateInterface(data); + showNotification(data.message, data.success ? 'success' : 'error'); + } catch (error) { + showNotification("Ошибка соединения с сервером", "error"); + } +} + +async function repairDetail() { + showLoading(); + try { + const response = await fetch('/repair', { + method: 'POST' + }); + const data = await response.json(); + updateInterface(data); + showNotification(data.message, data.success ? 'success' : 'info'); + } catch (error) { + showNotification("Ошибка соединения с сервером", "error"); + } +} + +async function executeTask() { + showLoading(); + try { + const response = await fetch('/execute_task', { + method: 'POST' + }); + const data = await response.json(); + updateInterface(data); + showNotification(data.message, 'success'); + } catch (error) { + showNotification("Ошибка соединения с сервером", "error"); + } +} + +async function resetField() { + showLoading(); + try { + const response = await fetch('/reset', { + method: 'POST' + }); + const data = await response.json(); + updateInterface(data); + showNotification("Поле успешно сброшено!", 'success'); + } catch (error) { + showNotification("Ошибка соединения с сервером", "error"); + } +} + +function updateInterface(data) { + document.getElementById('workshop-field').innerHTML = data.svg; + document.getElementById('robot-position').textContent = `(${data.robot_position.x}, ${data.robot_position.y})`; + document.getElementById('cell-type').textContent = data.cell_type; +} + +function showNotification(message, type) { + const container = document.getElementById('notification-container'); + + const notification = document.createElement('div'); + notification.className = `notification p-4 rounded-xl mb-3 shadow-lg text-white font-bold ${ + type === 'success' ? 'bg-green-600' : + type === 'error' ? 'bg-red-600' : + 'bg-blue-600' + }`; + + notification.innerHTML = ` +
+ + ${message} +
+ `; + + container.appendChild(notification); + + setTimeout(() => { + notification.style.opacity = '0'; + notification.style.transform = 'translateX(100%)'; + setTimeout(() => { + if (container.contains(notification)) { + container.removeChild(notification); + } + }, 500); + }, 3000); +} + +function showLoading() { + document.getElementById('workshop-field').innerHTML = ` +
+
+
+ `; +} + +document.addEventListener('DOMContentLoaded', function() { + document.body.style.opacity = '0'; + document.body.style.transition = 'opacity 0.8s ease-in-out'; + setTimeout(() => { + document.body.style.opacity = '1'; + }, 100); +}); \ No newline at end of file diff --git "a/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/templates/index.html" "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/templates/index.html" new file mode 100644 index 0000000..772d276 --- /dev/null +++ "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/step2/templates/index.html" @@ -0,0 +1,177 @@ + + + + + + 🤖 Робот-механик | Веб-лаборатория + + + + + + +
+ +
+

+ Робот-механик +

+

Интерактивная лабораторная работа

+
+ + +
+ +
+
+

Рабочая зона

+
+
+ {{ svg|safe }} +
+
+ + +
+
+

+ Управление +

+ + +
+ + + + + + +
+ + +
+ + + +
+
+ + +
+

+ Информация +

+ +
+

Текущая ячейка:

+

+ {{ cell_type }} +

+
+ +
+

Позиция робота:

+

+ ({{ robot_position.x }}, {{ robot_position.y }}) +

+
+ +
+

Легенда

+
+
+
+ Деталь +
+
+
+ Починено +
+
+
+ Инструмент +
+
+
+ Препятствие +
+
+
+ Масло +
+
+
+ Финиш +
+
+
+ Робот +
+
+
+ Пол +
+
+
+
+
+
+ + +
+ +
+ + +
+

Лабораторная работа по программированию | Робот-механик

+

Современный веб-интерфейс с использованием FastAPI и Tailwind CSS

+
+
+ + + + \ No newline at end of file diff --git "a/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/\320\267\320\260\320\277\320\270\321\201\321\214.mp4" "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/\320\267\320\260\320\277\320\270\321\201\321\214.mp4" new file mode 100644 index 0000000..8d95fdf Binary files /dev/null and "b/5030102_30201/\320\232\321\200\321\203\321\202\321\217\320\275\321\201\320\272\320\270\320\271 \320\240\320\276\320\274\320\260\320\275 \320\230\320\263\320\276\321\200\320\265\320\262\320\270\321\207/\320\267\320\260\320\277\320\270\321\201\321\214.mp4" differ