diff --git "a/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/1 \321\215\321\202\320\260\320\277_\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260 (1).mp4" "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/1 \321\215\321\202\320\260\320\277_\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260 (1).mp4"
new file mode 100644
index 0000000..fbd9be1
Binary files /dev/null and "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/1 \321\215\321\202\320\260\320\277_\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260 (1).mp4" differ
diff --git "a/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/2 \321\215\321\202\320\260\320\277_\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260 (1).mp4" "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/2 \321\215\321\202\320\260\320\277_\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260 (1).mp4"
new file mode 100644
index 0000000..1d87fe8
Binary files /dev/null and "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/2 \321\215\321\202\320\260\320\277_\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260 (1).mp4" differ
diff --git "a/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/README.txt" "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/README.txt"
new file mode 100644
index 0000000..108ca63
--- /dev/null
+++ "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/README.txt"
@@ -0,0 +1,3 @@
+Комментарии ко второму этапу (видео)
+
+Запускала через virtual env, так как в PyCharm версия питона 3.13.5 отказывалась работать с flask (несовместимость версий). Пробовала переустановить версию на 3.12.7 но заработало только в виртуальной на консоли.
diff --git "a/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/app.py" "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/app.py"
new file mode 100644
index 0000000..7981e10
--- /dev/null
+++ "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/app.py"
@@ -0,0 +1,256 @@
+from __future__ import annotations
+
+from flask import Flask, render_template, jsonify, request # Добавлен request
+from typing import List, Tuple, Optional
+from collections import deque
+from enum import Enum
+import math
+
+app = Flask(__name__)
+
+
+class ТипЯчеекПроект(Enum):
+ Пол = "Пол"
+ Модуль = "Модуль"
+ Инструмент = "Инструмент"
+ Препятствие = "Препятствие"
+ Финиш = "Финиш"
+ Контур = "Контур"
+
+
+class Ячейка:
+ def __init__(self, тип: ТипЯчеекПроект = ТипЯчеекПроект.Пол):
+ self.тип = тип
+ self.робот = False
+
+
+class Лабиринт:
+ def __init__(self, ширина: int = 10, длина: int = 8):
+ self.ширина = ширина
+ self.длина = длина
+ self.поле: List[List[Ячейка]] = [[Ячейка() for _ in range(ширина)] for _ in range(длина)]
+ self.поле[длина - 1][ширина - 1].тип = ТипЯчеекПроект.Финиш
+ self.поле[0][0].робот = True
+
+ def получить_соседа(self, x: int, y: int, направление: str) -> Optional[Tuple[int, int]]:
+ offsets = {
+ "ИнжВперёд": (0, 1), # вниз
+ "ИнжНазад": (0, -1), # вверх
+ "ИнжЛево": (-1, 0), # влево
+ "ИнжПраво": (1, 0), # вправо
+ "ДиагИнжВ": (-1, 1), # влево-вниз
+ "ДиагИнжН": (1, 1), # вправо-вниз
+ }
+ dx, dy = offsets.get(направление, (0, 0))
+ nx, ny = x + dx, y + dy
+ if 0 <= nx < self.ширина and 0 <= ny < self.длина:
+ cell = self.поле[ny][nx]
+ if cell.тип not in (ТипЯчеекПроект.Инструмент, ТипЯчеекПроект.Препятствие):
+ return nx, ny
+ return None
+
+ def сериализовать(self):
+ return [
+ [
+ {"type": cell.тип.value, "robot": cell.робот}
+ for cell in row
+ ]
+ for row in self.поле
+ ]
+
+
+maze = Лабиринт()
+
+
+@app.route('/')
+def index():
+ return render_template('index.html')
+
+
+@app.route('/get_maze')
+def get_maze():
+ return jsonify({"maze": maze.сериализовать(), "status": "Готово"})
+
+
+@app.route('/click', methods=['POST'])
+def click():
+ data = request.json
+ x = data['x']
+ y = data['y']
+ button = data['button']
+
+ if button == 'right':
+ for row in maze.поле:
+ for cell in row:
+ cell.робот = False
+ if maze.поле[y][x].тип not in (ТипЯчеекПроект.Инструмент, ТипЯчеекПроект.Препятствие):
+ maze.поле[y][x].робот = True
+ else:
+ order = [
+ ТипЯчеекПроект.Пол,
+ ТипЯчеекПроект.Модуль,
+ ТипЯчеекПроект.Инструмент,
+ ТипЯчеекПроект.Препятствие,
+ ТипЯчеекПроект.Финиш,
+ ТипЯчеекПроект.Контур,
+ ]
+ current = maze.поле[y][x].тип
+ idx = order.index(current)
+ maze.поле[y][x].тип = order[(idx + 1) % len(order)]
+
+ return jsonify({"maze": maze.сериализовать(), "status": "Изменено"})
+
+
+def найти_путь_к_цели(start_x: int, start_y: int, target_x: int, target_y: int) -> Optional[str]:
+ """Находит направление для движения к цели с использованием BFS"""
+ if (start_x, start_y) == (target_x, target_y):
+ return None
+
+ # Приоритет направлений (сначала прямые, потом диагонали)
+ directions = [
+ ("ИнжПраво", (1, 0)),
+ ("ИнжЛево", (-1, 0)),
+ ("ИнжВперёд", (0, 1)),
+ ("ИнжНазад", (0, -1)),
+ ("ДиагИнжН", (1, 1)),
+ ("ДиагИнжВ", (-1, 1)),
+ ]
+
+ # Простой поиск: выбираем направление, которое приближает к цели
+ best_dir = None
+ min_dist = float('inf')
+
+ for dir_name, (dx, dy) in directions:
+ nx, ny = start_x + dx, start_y + dy
+ if 0 <= nx < maze.ширина and 0 <= ny < maze.длина:
+ cell = maze.поле[ny][nx]
+ if cell.тип in (ТипЯчеекПроект.Инструмент, ТипЯчеекПроект.Препятствие):
+ continue
+ dist = abs(nx - target_x) + abs(ny - target_y)
+ if dist < min_dist:
+ min_dist = dist
+ best_dir = dir_name
+
+ return best_dir
+
+
+@app.route('/step')
+def step():
+ # Находим позицию робота
+ robot_pos = None
+ for y in range(maze.длина):
+ for x in range(maze.ширина):
+ if maze.поле[y][x].робот:
+ robot_pos = (x, y)
+ break
+ if robot_pos:
+ break
+
+ if not robot_pos:
+ return jsonify({"maze": maze.сериализовать(), "status": "Робот не найден", "finished": True})
+
+ x, y = robot_pos
+
+ # Обработка текущей клетки
+ if maze.поле[y][x].тип == ТипЯчеекПроект.Пол:
+ maze.поле[y][x].тип = ТипЯчеекПроект.Модуль
+ elif maze.поле[y][x].тип == ТипЯчеекПроект.Модуль:
+ maze.поле[y][x].тип = ТипЯчеекПроект.Контур
+
+ # Проверяем, остались ли необработанные клетки
+ targets_left = False
+ for row in maze.поле:
+ for cell in row:
+ if cell.тип in (ТипЯчеекПроект.Пол, ТипЯчеекПроект.Модуль):
+ targets_left = True
+ break
+ if targets_left:
+ break
+
+ # Если нет целей и робот на финише - завершаем
+ if not targets_left and (x, y) == (maze.ширина - 1, maze.длина - 1):
+ return jsonify({
+ "maze": maze.сериализовать(),
+ "status": "Задача завершена!",
+ "finished": True
+ })
+
+ # Находим цель
+ if targets_left:
+ # Находим ближайшую необработанную клетку
+ targets = []
+ for yy in range(maze.длина):
+ for xx in range(maze.ширина):
+ if maze.поле[yy][xx].тип in (ТипЯчеекПроект.Пол, ТипЯчеекПроект.Модуль):
+ targets.append((xx, yy))
+
+ # Выбираем цель с минимальным расстоянием
+ tx, ty = min(targets, key=lambda t: abs(t[0] - x) + abs(t[1] - y))
+ else:
+ # Двигаемся к финишу
+ tx, ty = maze.ширина - 1, maze.длина - 1
+
+ # Находим направление для движения
+ direction = найти_путь_к_цели(x, y, tx, ty)
+
+ if direction:
+ # Пытаемся сделать шаг в выбранном направлении
+ new_pos = maze.получить_соседа(x, y, direction)
+ if new_pos:
+ nx, ny = new_pos
+ maze.поле[y][x].робот = False
+ maze.поле[ny][nx].робот = True
+ status = "Выполняется..."
+ else:
+ status = "Не могу двигаться в этом направлении"
+ else:
+ status = "На месте"
+
+ # Проверяем завершение после движения
+ if not targets_left and (nx if direction else x, ny if direction else y) == (maze.ширина - 1, maze.длина - 1):
+ status = "Задача завершена!"
+ return jsonify({
+ "maze": maze.сериализовать(),
+ "status": status,
+ "finished": True
+ })
+
+ return jsonify({
+ "maze": maze.сериализовать(),
+ "status": status,
+ "finished": False
+ })
+
+
+@app.route('/run', methods=['POST'])
+def run_toggle():
+ return jsonify({"running": True})
+
+
+@app.route('/reset')
+def reset():
+ global maze
+ maze = Лабиринт()
+ return jsonify({"maze": maze.сериализовать(), "status": "Сброшено"})
+
+
+@app.route('/demo')
+def demo():
+ global maze
+ maze = Лабиринт()
+
+ # Демо-лабиринт
+ for x in range(2, 8):
+ maze.поле[3][x].тип = ТипЯчеекПроект.Препятствие
+ maze.поле[2][3].тип = ТипЯчеекПроект.Инструмент
+ maze.поле[1][1].тип = ТипЯчеекПроект.Модуль
+ maze.поле[1][8].тип = ТипЯчеекПроект.Модуль
+ maze.поле[5][2].тип = ТипЯчеекПроект.Пол
+ maze.поле[6][5].тип = ТипЯчеекПроект.Пол
+ maze.поле[4][7].тип = ТипЯчеекПроект.Пол
+
+ return jsonify({"maze": maze.сериализовать(), "status": "Демо загружено"})
+
+
+if __name__ == '__main__':
+ app.run(debug=True, port=5000)
diff --git "a/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/index.html" "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/index.html"
new file mode 100644
index 0000000..13eccac
--- /dev/null
+++ "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/index.html"
@@ -0,0 +1,24 @@
+
+
+
+
+ РоботИнженер — Web
+
+
+
+
+ РоботИнженер (веб-версия)
+
+
+
+
+
+
+
+ Загрузка...
+
+
+
+
diff --git "a/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/main.py" "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/main.py"
new file mode 100644
index 0000000..54f65a9
--- /dev/null
+++ "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/main.py"
@@ -0,0 +1,428 @@
+from __future__ import annotations
+
+from collections import deque
+from enum import Enum
+from typing import List, Optional, Tuple, Dict
+
+from PySide6.QtCore import QTimer, Qt
+from PySide6.QtGui import QBrush, QPen, QFont
+from PySide6.QtWidgets import (
+ QApplication,
+ QHBoxLayout,
+ QLabel,
+ QMainWindow,
+ QPushButton,
+ QVBoxLayout,
+ QWidget,
+ QGraphicsEllipseItem,
+ QGraphicsRectItem,
+ QGraphicsScene,
+ QGraphicsTextItem,
+ QGraphicsView,
+)
+
+
+class ТипЯчеекПроект(Enum):
+ Пол = "Пол"
+ Модуль = "Модуль"
+ Инструмент = "Инструмент"
+ Препятствие = "Препятствие"
+ Финиш = "Финиш"
+ Контур = "Контур"
+
+
+class ТипНаправленийИнжНапр(Enum):
+ ИнжВперёд = "ИнжВперёд"
+ ИнжНазад = "ИнжНазад"
+ ИнжЛево = "ИнжЛево"
+ ИнжПраво = "ИнжПраво"
+ ДиагИнжВ = "ДиагИнжВ"
+ ДиагИнжН = "ДиагИнжН"
+
+
+class ЯчейкаРоботИнженер:
+ def __init__(self, тип_ячейки: ТипЯчеекПроект = ТипЯчеекПроект.Пол, ячейка_робота: bool = False):
+ self.тип_ячейки = тип_ячейки
+ self.ячейка_робота = ячейка_робота
+ self.x = 0
+ self.y = 0
+
+
+class ЛабиринтРоботИнженер:
+ def __init__(self):
+ self.ширина = 0
+ self.длина = 0
+ self.ячейки: List[List[ЯчейкаРоботИнженер]] = []
+
+ def ИнициализироватьЛабиринт(self, тип_ячейки: ТипЯчеекПроект, ширина: int = 10, длина: int = 8):
+ self.ширина = ширина
+ self.длина = длина
+ self.ячейки = [[ЯчейкаРоботИнженер(тип_ячейки) for _ in range(ширина)] for _ in range(длина)]
+ for y in range(длина):
+ for x in range(ширина):
+ self.ячейки[y][x].x = x
+ self.ячейки[y][x].y = y
+
+ def ПолучитьСоседнююЯчейку(self, текущая: ЯчейкаРоботИнженер, направление: ТипНаправленийИнжНапр) -> Optional[ЯчейкаРоботИнженер]:
+ offsets = {
+ ТипНаправленийИнжНапр.ИнжВперёд: (0, 1),
+ ТипНаправленийИнжНапр.ИнжНазад: (0, -1),
+ ТипНаправленийИнжНапр.ИнжЛево: (-1, 0),
+ ТипНаправленийИнжНапр.ИнжПраво: (1, 0),
+ ТипНаправленийИнжНапр.ДиагИнжВ: (-1, 1),
+ ТипНаправленийИнжНапр.ДиагИнжН: (1, 1),
+ }
+ dx, dy = offsets[направление]
+ nx, ny = текущая.x + dx, текущая.y + dy
+ if 0 <= nx < self.ширина and 0 <= ny < self.длина:
+ cell = self.ячейки[ny][nx]
+ if cell.тип_ячейки not in (ТипЯчеекПроект.Инструмент, ТипЯчеекПроект.Препятствие):
+ return cell
+ return None
+
+
+class РоботИнженер:
+ def __init__(self, лабиринт: ЛабиринтРоботИнженер):
+ self.лабиринт = лабиринт
+ self.текущая_ячейка: Optional[ЯчейкаРоботИнженер] = None
+ for row in лабиринт.ячейки:
+ for cell in row:
+ if cell.ячейка_робота:
+ self.текущая_ячейка = cell
+ return
+
+ def _переместиться(self, новая: Optional[ЯчейкаРоботИнженер]) -> bool:
+ if not новая:
+ return False
+ if self.текущая_ячейка:
+ self.текущая_ячейка.ячейка_робота = False
+ self.текущая_ячейка = новая
+ новая.ячейка_робота = True
+ return True
+
+ def Перейти(self, направление: ТипНаправленийИнжНапр):
+ сосед = self.лабиринт.ПолучитьСоседнююЯчейку(self.текущая_ячейка, направление)
+ return self._переместиться(сосед)
+
+ def ДвигИнжВперёд(self): return self.Перейти(ТипНаправленийИнжНапр.ИнжВперёд)
+ def ДвигИнжНазад(self): return self.Перейти(ТипНаправленийИнжНапр.ИнжНазад)
+ def ДвигИнжЛево(self): return self.Перейти(ТипНаправленийИнжНапр.ИнжЛево)
+ def ДвигИнжПраво(self): return self.Перейти(ТипНаправленийИнжНапр.ИнжПраво)
+ def ДиагИнжВ(self): return self.Перейти(ТипНаправленийИнжНапр.ДиагИнжВ)
+ def ДиагИнжН(self): return self.Перейти(ТипНаправленийИнжНапр.ДиагИнжН)
+
+ def Пол(self):
+ if self.текущая_ячейка and self.текущая_ячейка.тип_ячейки == ТипЯчеекПроект.Пол:
+ self.текущая_ячейка.тип_ячейки = ТипЯчеекПроект.Модуль
+
+ def Модуль(self):
+ if self.текущая_ячейка and self.текущая_ячейка.тип_ячейки == ТипЯчеекПроект.Модуль:
+ self.текущая_ячейка.тип_ячейки = ТипЯчеекПроект.Контур
+
+
+class MissionRuntime:
+ def __init__(self, maze: ЛабиринтРоботИнженер, robot: РоботИнженер):
+ self.maze = maze
+ self.robot = robot
+ self.path: List[str] = []
+ self.target = None
+ self.finish = next((c for row in maze.ячейки for c in row if c.тип_ячейки == ТипЯчеекПроект.Финиш), None)
+ self.done = False
+
+ def _find_path(self, start: ЯчейкаРоботИнженер, goal: ЯчейкаРоботИнженер) -> List[str]:
+ if start == goal:
+ return []
+ queue = deque([start])
+ came_from: Dict[ЯчейкаРоботИнженер, Tuple[ЯчейкаРоботИнженер, str]] = {start: (start, "")}
+ while queue:
+ curr = queue.popleft()
+ if curr == goal:
+ path = []
+ while came_from[curr][1]:
+ path.append(came_from[curr][1])
+ curr = came_from[curr][0]
+ path.reverse()
+ return path
+ for d in ТипНаправленийИнжНапр:
+ neigh = self.maze.ПолучитьСоседнююЯчейку(curr, d)
+ if neigh and neigh not in came_from:
+ came_from[neigh] = (curr, d.value)
+ queue.append(neigh)
+ return []
+
+ def step(self):
+ if self.done or not self.robot.текущая_ячейка:
+ return
+
+ # Обработка текущей клетки
+ self.robot.Пол()
+ self.robot.Модуль()
+
+ # Есть ли цели?
+ targets = [c for row in self.maze.ячейки for c in row if c.тип_ячейки in (ТипЯчеекПроект.Пол, ТипЯчеекПроект.Модуль)]
+ if targets:
+ if not self.path or self.target not in targets:
+ best = min(targets, key=lambda t: len(self._find_path(self.robot.текущая_ячейка, t)) if self._find_path(self.robot.текущая_ячейка, t) else float('inf'), default=None)
+ if best:
+ self.target = best
+ self.path = self._find_path(self.robot.текущая_ячейка, best)
+ if self.path:
+ move = self.path.pop(0)
+ mapping = {
+ ТипНаправленийИнжНапр.ИнжВперёд.value: self.robot.ДвигИнжВперёд,
+ ТипНаправленийИнжНапр.ИнжНазад.value: self.robot.ДвигИнжНазад,
+ ТипНаправленийИнжНапр.ИнжЛево.value: self.robot.ДвигИнжЛево,
+ ТипНаправленийИнжНапр.ИнжПраво.value: self.robot.ДвигИнжПраво,
+ ТипНаправленийИнжНапр.ДиагИнжВ.value: self.robot.ДиагИнжВ,
+ ТипНаправленийИнжНапр.ДиагИнжН.value: self.robot.ДиагИнжН,
+ }
+ mapping[move]()
+ else:
+ # Идём на финиш
+ if self.finish and self.robot.текущая_ячейка != self.finish:
+ if not self.path:
+ self.path = self._find_path(self.robot.текущая_ячейка, self.finish)
+ if self.path:
+ move = self.path.pop(0)
+ mapping = {
+ ТипНаправленийИнжНапр.ИнжВперёд.value: self.robot.ДвигИнжВперёд,
+ ТипНаправленийИнжНапр.ИнжНазад.value: self.robot.ДвигИнжНазад,
+ ТипНаправленийИнжНапр.ИнжЛево.value: self.robot.ДвигИнжЛево,
+ ТипНаправленийИнжНапр.ИнжПраво.value: self.robot.ДвигИнжПраво,
+ ТипНаправленийИнжНапр.ДиагИнжВ.value: self.robot.ДиагИнжВ,
+ ТипНаправленийИнжНапр.ДиагИнжН.value: self.robot.ДиагИнжН,
+ }
+ mapping[move]()
+ else:
+ self.done = True
+
+
+class MazeEditor:
+ def __init__(self, maze: ЛабиринтРоботИнженер):
+ self.maze = maze
+
+ def place_robot(self, x: int, y: int):
+ for row in self.maze.ячейки:
+ for c in row:
+ c.ячейка_робота = False
+ if 0 <= y < self.maze.длина and 0 <= x < self.maze.ширина:
+ c = self.maze.ячейки[y][x]
+ if c.тип_ячейки not in (ТипЯчеекПроект.Инструмент, ТипЯчеекПроект.Препятствие):
+ c.ячейка_робота = True
+
+ def set_cell_type(self, x: int, y: int, t: ТипЯчеекПроект):
+ if 0 <= y < self.maze.длина and 0 <= x < self.maze.ширина:
+ self.maze.ячейки[y][x].тип_ячейки = t
+
+ def cycle_cell_type(self, x: int, y: int):
+ order = [ТипЯчеекПроект.Пол, ТипЯчеекПроект.Модуль, ТипЯчеекПроект.Инструмент,
+ ТипЯчеекПроект.Препятствие, ТипЯчеекПроект.Финиш, ТипЯчеекПроект.Контур]
+ if 0 <= y < self.maze.длина and 0 <= x < self.maze.ширина:
+ c = self.maze.ячейки[y][x]
+ idx = (order.index(c.тип_ячейки) + 1) % len(order)
+ self.set_cell_type(x, y, order[idx])
+
+
+class MazeView(QGraphicsView):
+ def __init__(self, parent: "MainWindow"):
+ super().__init__()
+ self.parent = parent
+
+ def mousePressEvent(self, event):
+ if self.parent.running:
+ return
+ scene_pos = self.mapToScene(event.position().toPoint())
+ x, y = self.parent.scene_to_world(scene_pos.x(), scene_pos.y())
+ if x is None or y is None:
+ return
+ if event.button() == Qt.LeftButton:
+ self.parent.editor.cycle_cell_type(x, y)
+ elif event.button() == Qt.RightButton:
+ self.parent.editor.place_robot(x, y)
+ self.parent.redraw()
+
+
+class MainWindow(QMainWindow):
+ def __init__(self):
+ super().__init__()
+ self.setWindowTitle("РоботИнженер")
+
+ self.cell_size = 44
+ self.margin = 12
+
+ self.maze = ЛабиринтРоботИнженер()
+ self.maze.ИнициализироватьЛабиринт(ТипЯчеекПроект.Пол, 10, 8)
+ self.editor = MazeEditor(self.maze)
+ self.editor.set_cell_type(9, 7, ТипЯчеекПроект.Финиш)
+ self.editor.place_robot(0, 0)
+
+ self.robot = None
+ self.runtime = None
+ self.running = False
+ self.steps = 0
+
+ self.scene = QGraphicsScene()
+ self.view = MazeView(self)
+ self.view.setScene(self.scene)
+
+ self.status = QLabel("ЛКМ: тип клетки | ПКМ: робот")
+ layout = QVBoxLayout()
+ layout.addWidget(self.view)
+
+ controls = QHBoxLayout()
+ for text, slot in [("Run", self.on_run), ("Step", self.on_step), ("Reset", self.on_reset), ("Demo", self.on_demo)]:
+ btn = QPushButton(text)
+ btn.clicked.connect(slot)
+ controls.addWidget(btn)
+
+ layout.addLayout(controls)
+ layout.addWidget(self.status)
+
+ central = QWidget()
+ central.setLayout(layout)
+ self.setCentralWidget(central)
+
+ self.timer = QTimer(self)
+ self.timer.timeout.connect(self.tick)
+
+ self.redraw()
+
+ def view_width(self) -> int:
+ return self.margin * 2 + self.maze.ширина * self.cell_size
+
+ def view_height(self) -> int:
+ return self.margin * 2 + self.maze.длина * self.cell_size
+
+ def world_to_scene(self, x: int, y: int) -> Tuple[float, float]:
+ sx = self.margin + x * self.cell_size
+ sy = self.margin + (self.maze.длина - 1 - y) * self.cell_size
+ return sx, sy
+
+ def scene_to_world(self, sx: float, sy: float) -> Tuple[Optional[int], Optional[int]]:
+ grid_x = sx - self.margin
+ grid_y = sy - self.margin
+ if grid_x < 0 or grid_y < 0:
+ return None, None
+ cell_x = int(grid_x // self.cell_size)
+ cell_y = int(grid_y // self.cell_size)
+ if cell_x >= self.maze.ширина or cell_y >= self.maze.длина:
+ return None, None
+ world_y = self.maze.длина - 1 - cell_y
+ return cell_x, world_y
+
+ def redraw(self):
+ self.scene.clear()
+ self.scene.setSceneRect(0, 0, self.view_width(), self.view_height())
+
+ for y in range(self.maze.длина - 1, -1, -1):
+ for x in range(self.maze.ширина):
+ c = self.maze.ячейки[y][x]
+ sx, sy = self.world_to_scene(x, y)
+ rect = QGraphicsRectItem(sx, sy, self.cell_size, self.cell_size)
+ rect.setPen(QPen(Qt.black))
+ rect.setBrush(QBrush(self.get_color(c.тип_ячейки)))
+ self.scene.addItem(rect)
+
+ label = self.get_label(c.тип_ячейки)
+ if label:
+ text = QGraphicsTextItem(label)
+ text.setDefaultTextColor(Qt.black)
+ text.setFont(QFont("Arial", 12, QFont.Bold))
+ text.setPos(sx + 14, sy + 10)
+ self.scene.addItem(text)
+
+ if c.ячейка_робота:
+ r = self.cell_size * 0.28
+ circle = QGraphicsEllipseItem(sx + self.cell_size / 2 - r, sy + self.cell_size / 2 - r, 2 * r, 2 * r)
+ circle.setBrush(QBrush(Qt.black))
+ self.scene.addItem(circle)
+
+ self.update_status()
+
+ def get_color(self, t: ТипЯчеекПроект):
+ colors = {
+ ТипЯчеекПроект.Пол: Qt.white,
+ ТипЯчеекПроект.Модуль: Qt.yellow,
+ ТипЯчеекПроект.Инструмент: Qt.cyan,
+ ТипЯчеекПроект.Препятствие: Qt.darkGray,
+ ТипЯчеекПроект.Финиш: Qt.red,
+ ТипЯчеекПроект.Контур: Qt.green,
+ }
+ return colors.get(t, Qt.white)
+
+ def get_label(self, t: ТипЯчеекПроект) -> str:
+ labels = {
+ ТипЯчеекПроект.Пол: "П",
+ ТипЯчеекПроект.Модуль: "М",
+ ТипЯчеекПроект.Инструмент: "И",
+ ТипЯчеекПроект.Препятствие: "Б",
+ ТипЯчеекПроект.Финиш: "Ф",
+ ТипЯчеекПроект.Контур: "К",
+ }
+ return labels.get(t, "")
+
+ def update_status(self):
+ remaining = sum(1 for row in self.maze.ячейки for c in row if c.тип_ячейки in (ТипЯчеекПроект.Пол, ТипЯчеекПроект.Модуль))
+ pos = f"({self.robot.текущая_ячейка.x},{self.robot.текущая_ячейка.y})" if self.robot and self.robot.текущая_ячейка else "не размещён"
+ self.status.setText(f"Робот: {pos} | Осталось П/М: {remaining} | Шаги: {self.steps}")
+
+ def prepare_runtime(self):
+ self.robot = РоботИнженер(self.maze)
+ self.runtime = MissionRuntime(self.maze, self.robot)
+ self.steps = 0
+
+ def on_run(self):
+ if self.running:
+ return
+ self.prepare_runtime()
+ self.running = True
+ self.timer.start(100)
+
+ def on_step(self):
+ if self.running:
+ return
+ if not self.runtime:
+ self.prepare_runtime()
+ self.runtime.step()
+ self.steps += 1
+ self.redraw()
+
+ def tick(self):
+ if not self.running:
+ return
+ self.runtime.step()
+ self.steps += 1
+ if self.runtime.done:
+ self.running = False
+ self.timer.stop()
+ self.redraw()
+
+ def on_reset(self):
+ if self.running:
+ return
+ self.maze.ИнициализироватьЛабиринт(ТипЯчеекПроект.Пол, 10, 8)
+ self.editor = MazeEditor(self.maze)
+ self.editor.set_cell_type(9, 7, ТипЯчеекПроект.Финиш)
+ self.editor.place_robot(0, 0)
+ self.runtime = None
+ self.running = False
+ self.redraw()
+
+ def on_demo(self):
+ if self.running:
+ return
+ self.on_reset()
+ for x in range(2, 8):
+ self.editor.set_cell_type(x, 3, ТипЯчеекПроект.Препятствие)
+ self.editor.set_cell_type(3, 2, ТипЯчеекПроект.Инструмент)
+ self.editor.set_cell_type(1, 1, ТипЯчеекПроект.Модуль)
+ self.editor.set_cell_type(4, 4, ТипЯчеекПроект.Пол)
+ self.redraw()
+
+
+if __name__ == "__main__":
+ import sys
+ app = QApplication(sys.argv)
+ window = MainWindow()
+ window.show()
+ sys.exit(app.exec())
\ No newline at end of file
diff --git "a/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/mapping.yaml" "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/mapping.yaml"
new file mode 100644
index 0000000..3c7953d
--- /dev/null
+++ "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/mapping.yaml"
@@ -0,0 +1,31 @@
+классы:
+ - РоботИнженер: РоботИнженер
+ свойства:
+ - лабиринт: лабиринт
+ методы:
+ - ДвигИнжВперёд: ДвигИнжВперёд
+ - ДвигИнжНазад: ДвигИнжНазад
+ - ДвигИнжЛево: ДвигИнжЛево
+ - ДвигИнжПраво: ДвигИнжПраво
+ - ДиагИнжВ: ДиагИнжВ
+ - ДиагИнжН: ДиагИнжН
+ - Модуль: Модуль
+ - Пол: Пол
+
+перечисления:
+ - ТипНаправленийИнжНапр: ТипНаправленийИнжНапр
+ опции:
+ - ИнжВперёд: ИнжВперёд
+ - ИнжНазад: ИнжНазад
+ - ИнжЛево: ИнжЛево
+ - ИнжПраво: ИнжПраво
+ - ДиагИнжВ: ДиагИнжВ
+ - ДиагИнжН: ДиагИнжН
+ - ТипЯчеекПроект: ТипЯчеекПроект
+ опции:
+ - Пол: Пол
+ - Модуль: Модуль
+ - Инструмент: Инструмент
+ - Препятствие: Препятствие
+ - Финиш: Финиш
+ - Контур: Контур
\ No newline at end of file
diff --git "a/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/script.js" "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/script.js"
new file mode 100644
index 0000000..c2e69eb
--- /dev/null
+++ "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/script.js"
@@ -0,0 +1,105 @@
+const CELL_LABELS = {
+ "Пол": "П",
+ "Модуль": "М",
+ "Инструмент": "И",
+ "Препятствие": "Б",
+ "Финиш": "Ф",
+ "Контур": "К"
+};
+
+const CELL_CLASSES = {
+ "Пол": "pol",
+ "Модуль": "modul",
+ "Инструмент": "instrument",
+ "Препятствие": "prepyatstvie",
+ "Финиш": "finish",
+ "Контур": "kontur"
+};
+
+let intervalId = null;
+
+function renderMaze(data) {
+ const table = $('#maze');
+ table.empty();
+ data.forEach((row, y) => {
+ const tr = $('');
+ row.forEach((cell, x) => {
+ const td = $('| ')
+ .addClass(CELL_CLASSES[cell.type])
+ .text(CELL_LABELS[cell.type])
+ .data('x', x)
+ .data('y', y);
+ if (cell.robot) td.addClass('robot');
+ tr.append(td);
+ });
+ table.append(tr);
+ });
+}
+
+function updateStatus(text) {
+ $('#status').text(text);
+}
+
+function loadMaze() {
+ $.get('/get_maze', data => {
+ renderMaze(data.maze);
+ updateStatus(data.status || "Готово");
+ });
+}
+
+$(document).ready(() => {
+ loadMaze();
+
+ $('#maze').on('click contextmenu', 'td', function(e) {
+ e.preventDefault();
+ const x = $(this).data('x');
+ const y = $(this).data('y');
+ const button = e.type === 'contextmenu' ? 'right' : 'left';
+ $.post('/click', JSON.stringify({x, y, button}), data => {
+ renderMaze(data.maze);
+ updateStatus(data.status || "Изменено");
+ });
+ });
+
+ $('#step').click(() => {
+ $.get('/step', data => {
+ renderMaze(data.maze);
+ updateStatus(data.status || "Шаг выполнен");
+ });
+ });
+
+ $('#run').click(function() {
+ if (intervalId) {
+ clearInterval(intervalId);
+ intervalId = null;
+ $(this).text('Run');
+ } else {
+ $(this).text('Pause');
+ intervalId = setInterval(() => {
+ $.get('/step', data => {
+ renderMaze(data.maze);
+ updateStatus(data.status);
+ if (data.finished) {
+ clearInterval(intervalId);
+ intervalId = null;
+ $('#run').text('Run');
+ }
+ });
+ }, 400);
+ }
+});
+
+ $('#reset').click(() => {
+ $.get('/reset', data => {
+ renderMaze(data.maze);
+ updateStatus(data.status);
+ });
+ });
+
+ $('#demo').click(() => {
+ $.get('/demo', data => {
+ renderMaze(data.maze);
+ updateStatus(data.status);
+ });
+ });
+});
diff --git "a/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/style.css" "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/style.css"
new file mode 100644
index 0000000..4eed38d
--- /dev/null
+++ "b/5030102_30202/\320\223\320\270\320\275\320\270\321\217\321\202\321\203\320\273\320\273\320\270\320\275\320\260\320\220\320\270\320\264\320\260/style.css"
@@ -0,0 +1,64 @@
+body {
+ font-family: Arial, sans-serif;
+ text-align: center;
+ background: #f4f4f4;
+}
+
+h1 {
+ color: #333;
+}
+
+#controls {
+ margin: 20px;
+}
+
+button {
+ padding: 10px 20px;
+ font-size: 16px;
+ margin: 5px;
+}
+
+#maze-container {
+ margin: 20px auto;
+ display: inline-block;
+ border: 3px solid #333;
+}
+
+table {
+ border-collapse: collapse;
+}
+
+td {
+ width: 50px;
+ height: 50px;
+ border: 1px solid #333;
+ text-align: center;
+ font-weight: bold;
+ font-size: 20px;
+ position: relative;
+}
+
+.pol { background: white; }
+.modul { background: yellow; }
+.instrument { background: cyan; }
+.prepyatstvie { background: gray; color: white; }
+.finish { background: red; color: white; }
+.kontur { background: green; color: white; }
+
+.robot::after {
+ content: "";
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 30px;
+ height: 30px;
+ background: black;
+ border-radius: 50%;
+ transform: translate(-50%, -50%);
+}
+
+#status {
+ margin-top: 20px;
+ font-size: 18px;
+ font-weight: bold;
+}
|