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; +}