diff --git "a/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/Lab1/desktop.py" "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/Lab1/desktop.py" new file mode 100644 index 0000000..777269b --- /dev/null +++ "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/Lab1/desktop.py" @@ -0,0 +1,337 @@ +# desktop.py - Этап 1: Десктопное приложение для РоботОператор + +import tkinter as tk +from tkinter import messagebox +from collections import deque +from main import ( + DirectionTypePanelDir, + CellTypePanel, + OperatorRobotMaze, + OperatorRobotCell, + OperatorRobot +) + + +class OperatorRobotVisualizer: + + def __init__(self, robot: OperatorRobot, cell_size=60): + self.robot = robot + self.cell_size = cell_size + + self.root = tk.Tk() + self.root.title("Робот Оператор - Панели") + + self.colors = { + CellTypePanel.FLOOR.value: "white", + CellTypePanel.PANEL.value: "lightblue", + CellTypePanel.ACTIVE.value: "green", + CellTypePanel.SLUICE.value: "gray", + CellTypePanel.WALL.value: "black", + CellTypePanel.BLOCK.value: "darkgray", + CellTypePanel.FINISH.value: "gold" + } + + canvas_width = robot.maze.width * cell_size + canvas_height = robot.maze.height * cell_size + self.canvas = tk.Canvas( + self.root, + width=canvas_width, + height=canvas_height, + bg="white" + ) + self.canvas.pack(padx=10, pady=10) + + self.info_label = tk.Label( + self.root, + text="Готов к работе", + font=("Arial", 12) + ) + self.info_label.pack(pady=5) + + button_frame = tk.Frame(self.root) + button_frame.pack(pady=5) + + self.start_button = tk.Button( + button_frame, + text="Начать работу", + command=self.start_work + ) + self.start_button.pack(side=tk.LEFT, padx=5) + + self.step_button = tk.Button( + button_frame, + text="Один шаг", + command=self.make_step + ) + self.step_button.pack(side=tk.LEFT, padx=5) + + self.robot_marker = None + self.is_working = False + + self.draw_grid() + + def draw_grid(self): + self.canvas.delete("all") + + for y in range(self.robot.maze.height): + for x in range(self.robot.maze.width): + display_y = self.robot.maze.height - 1 - y + + cell = self.robot.maze.get_cell_by_coordinates(x, y) + if not cell: + continue + + color = self.colors.get(cell.cell_type.value, "white") + + x1 = x * self.cell_size + y1 = display_y * self.cell_size + x2 = x1 + self.cell_size + y2 = y1 + self.cell_size + + self.canvas.create_rectangle( + x1, y1, x2, y2, + fill=color, + outline="gray" + ) + + text = self._get_cell_label(cell.cell_type) + if text: + self.canvas.create_text( + (x1 + x2) / 2, (y1 + y2) / 2, + text=text, + font=("Arial", 10) + ) + + self.draw_robot() + + def _get_cell_label(self, cell_type: CellTypePanel) -> str: + labels = { + CellTypePanel.FLOOR: "П", + CellTypePanel.PANEL: "Пн", + CellTypePanel.ACTIVE: "А", + CellTypePanel.SLUICE: "Ш", + CellTypePanel.WALL: "■", + CellTypePanel.BLOCK: "▓", + CellTypePanel.FINISH: "F" + } + return labels.get(cell_type, "") + + def draw_robot(self): + if self.robot_marker: + self.canvas.delete(self.robot_marker) + + if not self.robot.current_cell: + return + + x = self.robot.current_cell.x + y = self.robot.current_cell.y + display_y = self.robot.maze.height - 1 - y + + x_center = x * self.cell_size + self.cell_size / 2 + y_center = display_y * self.cell_size + self.cell_size / 2 + radius = self.cell_size / 3 + + self.robot_marker = self.canvas.create_oval( + x_center - radius, y_center - radius, + x_center + radius, y_center + radius, + fill="red", outline="darkred", width=2 + ) + + self.canvas.create_text( + x_center, y_center, + text="R", + font=("Arial", 14, "bold"), + fill="white" + ) + + def update_info(self, text: str): + self.info_label.config(text=text) + + def _is_free(self, x: int, y: int) -> bool: + if not (0 <= x < self.robot.maze.width and 0 <= y < self.robot.maze.height): + return False + cell = self.robot.maze.get_cell_by_coordinates(x, y) + if not cell: + return False + return cell.cell_type not in (CellTypePanel.WALL, CellTypePanel.BLOCK) + + def _find_nearest_target(self): + targets = [] + for y in range(self.robot.maze.height): + for x in range(self.robot.maze.width): + cell = self.robot.maze.get_cell_by_coordinates(x, y) + if cell and cell.cell_type in (CellTypePanel.FLOOR, CellTypePanel.PANEL): + targets.append((x, y)) + + if targets: + return targets + + for y in range(self.robot.maze.height): + for x in range(self.robot.maze.width): + cell = self.robot.maze.get_cell_by_coordinates(x, y) + if cell and cell.cell_type == CellTypePanel.FINISH: + return [(x, y)] + + return [] + + def _bfs_next_step(self, start, goals): + goals = set(goals) + queue = deque([start]) + prev = {start: None} + + while queue: + x, y = queue.popleft() + + if (x, y) in goals: + cur = (x, y) + while prev[cur] is not None and prev[cur] != start: + cur = prev[cur] + return cur if cur != start else None + + neighbors = [ + (x + 1, y), + (x - 1, y), + (x, y + 1), + (x, y - 1), + (x - 1, y + 1), + (x + 1, y - 1) + ] + + for nx, ny in neighbors: + if (nx, ny) not in prev and self._is_free(nx, ny): + prev[(nx, ny)] = (x, y) + queue.append((nx, ny)) + + return None + + def move_robot(self) -> bool: + x, y = self.robot.current_cell.x, self.robot.current_cell.y + goals = self._find_nearest_target() + + if not goals: + self.update_info("Целей нет (все Пол/Панель обработаны)") + return False + + next_pos = self._bfs_next_step((x, y), goals) + + if next_pos is None: + self.update_info("Путь к цели не найден") + return False + + nx, ny = next_pos + + if nx == x + 1 and ny == y: + return self.robot.channel_right() is not None + elif nx == x - 1 and ny == y: + return self.robot.channel_left() is not None + elif nx == x and ny == y + 1: + return self.robot.channel_forward() is not None + elif nx == x and ny == y - 1: + return self.robot.channel_backward() is not None + elif nx == x - 1 and ny == y + 1: + return self.robot.lift_diag() is not None + elif nx == x + 1 and ny == y - 1: + return self.robot.descend_diag() is not None + + return False + + def make_step(self): + if not self.robot.current_cell: + self.update_info("Робот не найден!") + return + + current_cell = self.robot.current_cell + x, y = current_cell.x, current_cell.y + + if current_cell.cell_type == CellTypePanel.FLOOR: + self.robot.floor() + self.update_info(f"Позиция ({x}, {y}): Пол → Панель") + elif current_cell.cell_type == CellTypePanel.PANEL: + self.robot.panel() + self.update_info(f"Позиция ({x}, {y}): Панель → Активно") + elif current_cell.cell_type == CellTypePanel.FINISH: + self.update_info(f"Позиция ({x}, {y}): ФИНИШ! Работа завершена!") + self.draw_grid() + self.is_working = False + return + else: + self.update_info(f"Позиция ({x}, {y}): Ячейка не требует обработки") + + self.move_robot() + + self.draw_grid() + self.root.update() + + def start_work(self): + self.is_working = True + self.start_button.config(state=tk.DISABLED) + self.automatic_work() + + def automatic_work(self): + if not self.is_working: + self.start_button.config(state=tk.NORMAL) + return + + if not self.robot.current_cell: + self.update_info("Ошибка: робот не найден!") + self.is_working = False + self.start_button.config(state=tk.NORMAL) + return + + current_cell = self.robot.current_cell + + if current_cell.cell_type == CellTypePanel.FINISH: + self.update_info("РАБОТА ЗАВЕРШЕНА! Робот достиг финиша!") + self.is_working = False + self.start_button.config(state=tk.NORMAL) + return + + self.make_step() + + self.root.after(100, self.automatic_work) + + def run(self): + self.root.mainloop() + + +def create_test_grid(width: int, height: int): + cell_values = [] + + for y in range(height): + row = [] + for x in range(width): + if x == 0 and y == 0: + cell_value = CellTypePanel.FLOOR.value | 0x8 + elif x == width - 1 and y == height - 1: + cell_value = CellTypePanel.FINISH.value + elif (x == 2 and y == 1) or (x == 3 and y == 2): + cell_value = CellTypePanel.WALL.value + elif x == 1 and y == 2: + cell_value = CellTypePanel.BLOCK.value + elif (x + y) % 3 == 0: + cell_value = CellTypePanel.PANEL.value + else: + cell_value = CellTypePanel.FLOOR.value + + row.append(cell_value) + cell_values.append(row) + + return cell_values + + +def main(): + width = 7 + height = 5 + + cell_values = create_test_grid(width, height) + + maze = OperatorRobotMaze(cells=cell_values) + + robot = OperatorRobot(maze) + + visualizer = OperatorRobotVisualizer(robot) + visualizer.run() + + +if __name__ == "__main__": + main() diff --git "a/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/Lab1/lab1.mp4" "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/Lab1/lab1.mp4" new file mode 100644 index 0000000..ff76486 Binary files /dev/null and "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/Lab1/lab1.mp4" differ diff --git "a/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/Lab1/main.py" "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/Lab1/main.py" new file mode 100644 index 0000000..fc9bf88 --- /dev/null +++ "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/Lab1/main.py" @@ -0,0 +1,238 @@ +from enum import Enum +from typing import Optional, Iterator, List + + +class DirectionTypePanelDir(Enum): + CHANNEL_NORTH = "ChannelNorth" + CHANNEL_SOUTH = "ChannelSouth" + CHANNEL_WEST = "ChannelWest" + CHANNEL_EAST = "ChannelEast" + DIAG_NW = "DiagNorthWest" + DIAG_SE = "DiagSouthEast" + + +class CellTypePanel(Enum): + FLOOR = 0x0 + PANEL = 0x1 + ACTIVE = 0x2 + SLUICE = 0x3 + WALL = 0x4 + BLOCK = 0x5 + FINISH = 0x6 + + @classmethod + def from_value(cls, value: int) -> 'CellTypePanel': + base_value = value & 0x7 + for cell_type in cls: + if cell_type.value == base_value: + return cell_type + return cls.FLOOR + + +class OperatorRobotCell: + def __init__(self, x: int = 0, y: int = 0, cell_value: int = 0x0): + self.x = x + self.y = y + self.has_robot = (cell_value & 0x8) != 0 + self.cell_type = CellTypePanel.from_value(cell_value) + + +class OperatorRobotMaze: + def __init__(self, width: int = None, height: int = None, cells: List[List[int]] = None): + self.width = width if width is not None else 0 + self.height = height if height is not None else 0 + self.cells = [] + + if cells is not None: + self.load_from_values(cells) + elif width is not None and height is not None: + self.initialize_maze() + + def load_from_values(self, cell_values: List[List[int]]): + if not cell_values: + self.height = 0 + self.width = 0 + self.cells = [] + return + + self.height = len(cell_values) + self.width = len(cell_values[0]) if self.height > 0 else 0 + self.cells = [] + + for row_idx in range(self.height - 1, -1, -1): + y = row_idx + row = [] + for x in range(self.width): + cell_value = cell_values[row_idx][x] + cell = OperatorRobotCell(x, y, cell_value) + row.append(cell) + self.cells.append(row) + + def initialize_maze(self, cell_type: CellTypePanel = None): + if cell_type is None: + cell_type = CellTypePanel.FLOOR + + self.cells = [] + for list_y in range(self.height): + y = self.height - 1 - list_y + row = [] + for x in range(self.width): + cell_value = cell_type.value + cell = OperatorRobotCell(x, y, cell_value) + row.append(cell) + self.cells.append(row) + + def get_cell_by_coordinates(self, x: int, y: int) -> Optional[OperatorRobotCell]: + if 0 <= x < self.width and 0 <= y < self.height: + list_y = self.height - 1 - y + return self.cells[list_y][x] + return None + + def get_neighbor_cell(self, current_cell: OperatorRobotCell, + search_direction: DirectionTypePanelDir) -> Optional[OperatorRobotCell]: + if not self.cells or not current_cell: + return None + + x, y = current_cell.x, current_cell.y + + offsets = { + DirectionTypePanelDir.CHANNEL_NORTH.value: (0, 1), + DirectionTypePanelDir.CHANNEL_SOUTH.value: (0, -1), + DirectionTypePanelDir.CHANNEL_WEST.value: (-1, 0), + DirectionTypePanelDir.CHANNEL_EAST.value: (1, 0), + DirectionTypePanelDir.DIAG_NW.value: (-1, 1), + DirectionTypePanelDir.DIAG_SE.value: (1, -1), + } + + dx, dy = offsets.get(search_direction.value, (0, 0)) + return self.get_cell_by_coordinates(x + dx, y + dy) + + def get_iterator(self) -> Iterator[OperatorRobotCell]: + + class SnakeIterator: + def __init__(self, maze): + self.maze = maze + self.x = 0 + self.y = 0 + self.move_right = True + self.done = False + self.first = True + + def __iter__(self): + return self + + def __next__(self): + if self.done: + raise StopIteration + + if self.first: + self.first = False + cell = self.maze.get_cell_by_coordinates(self.x, self.y) + return cell + + if self.move_right: + if self.x < self.maze.width - 1: + self.x += 1 + else: + if self.y < self.maze.height - 1: + self.y += 1 + self.move_right = False + else: + self.done = True + else: + if self.x > 0: + self.x -= 1 + else: + if self.y < self.maze.height - 1: + self.y += 1 + self.move_right = True + else: + self.done = True + + if self.done: + raise StopIteration + + cell = self.maze.get_cell_by_coordinates(self.x, self.y) + return cell + + return SnakeIterator(self) + + +class OperatorRobot: + + def __init__(self, maze: OperatorRobotMaze = None): + self.maze = maze + self.current_cell = None + + if maze and maze.cells: + for y in range(maze.height): + for x in range(maze.width): + cell = maze.get_cell_by_coordinates(x, y) + if cell and cell.has_robot: + self.current_cell = cell + return + + def _move_to_cell(self, new_cell: Optional[OperatorRobotCell]) -> Optional[OperatorRobotCell]: + if new_cell and new_cell.cell_type not in [CellTypePanel.WALL, CellTypePanel.BLOCK]: + if self.current_cell: + self.current_cell.has_robot = False + self.current_cell = new_cell + new_cell.has_robot = True + return new_cell + return None + + def channel_forward(self) -> Optional[OperatorRobotCell]: + if self.maze and self.current_cell: + new_cell = self.maze.get_neighbor_cell( + self.current_cell, DirectionTypePanelDir.CHANNEL_NORTH + ) + return self._move_to_cell(new_cell) + return None + + def channel_backward(self) -> Optional[OperatorRobotCell]: + if self.maze and self.current_cell: + new_cell = self.maze.get_neighbor_cell( + self.current_cell, DirectionTypePanelDir.CHANNEL_SOUTH + ) + return self._move_to_cell(new_cell) + return None + + def channel_left(self) -> Optional[OperatorRobotCell]: + if self.maze and self.current_cell: + new_cell = self.maze.get_neighbor_cell( + self.current_cell, DirectionTypePanelDir.CHANNEL_WEST + ) + return self._move_to_cell(new_cell) + return None + + def channel_right(self) -> Optional[OperatorRobotCell]: + if self.maze and self.current_cell: + new_cell = self.maze.get_neighbor_cell( + self.current_cell, DirectionTypePanelDir.CHANNEL_EAST + ) + return self._move_to_cell(new_cell) + return None + + def lift_diag(self) -> Optional[OperatorRobotCell]: + if self.maze and self.current_cell: + new_cell = self.maze.get_neighbor_cell( + self.current_cell, DirectionTypePanelDir.DIAG_NW + ) + return self._move_to_cell(new_cell) + return None + + def descend_diag(self) -> Optional[OperatorRobotCell]: + if self.maze and self.current_cell: + new_cell = self.maze.get_neighbor_cell( + self.current_cell, DirectionTypePanelDir.DIAG_SE + ) + return self._move_to_cell(new_cell) + return None + + def panel(self): + if self.current_cell and self.current_cell.cell_type == CellTypePanel.PANEL: + self.current_cell.cell_type = CellTypePanel.ACTIVE + + def floor(self): + if self.current_cell and self.current_cell.cell_type == CellTypePanel.FLOOR: + self.current_cell.cell_type = CellTypePanel.PANEL diff --git "a/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/app.js" "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/app.js" new file mode 100644 index 0000000..3ed7d02 --- /dev/null +++ "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/app.js" @@ -0,0 +1,145 @@ +const WIDTH = 7; +const HEIGHT = 5; + +let maze; +let robot; +let isWorking = false; + +function createTestGrid(width, height) { + const cellValues = []; + for (let y = 0; y < height; y++) { + const row = []; + for (let x = 0; x < width; x++) { + let cellValue; + if (x === 0 && y === 0) { + cellValue = CellType.FLOOR | 0x8; + } else if (x === width - 1 && y === height - 1) { + cellValue = CellType.FINISH; + } else if ((x === 2 && y === 1) || (x === 3 && y === 2)) { + cellValue = CellType.WALL; + } else if (x === 1 && y === 2) { + cellValue = CellType.BLOCK; + } else if ((x + y) % 3 === 0) { + cellValue = CellType.PANEL; + } else { + cellValue = CellType.FLOOR; + } + row.push(cellValue); + } + cellValues.push(row); + } + return cellValues; +} + +function drawGrid() { + const grid = document.getElementById('grid'); + grid.innerHTML = ''; + grid.style.gridTemplateColumns = `repeat(${WIDTH}, 70px)`; + + for (let y = HEIGHT - 1; y >= 0; y--) { + for (let x = 0; x < WIDTH; x++) { + const cell = maze.getCell(x, y); + const cellDiv = document.createElement('div'); + cellDiv.className = 'cell'; + + const typeNames = ['floor', 'panel', 'active', 'sluice', 'wall', 'block', 'finish']; + const labels = ['П', 'Пн', 'А', 'Ш', '■', '▓', 'F']; + + cellDiv.classList.add(typeNames[cell.type] || 'floor'); + cellDiv.textContent = labels[cell.type] || ''; + + if (cell.hasRobot) { + const robotDiv = document.createElement('div'); + robotDiv.className = 'robot'; + robotDiv.textContent = 'R'; + cellDiv.appendChild(robotDiv); + } + + grid.appendChild(cellDiv); + } + } +} + +function updateInfo(text) { + document.getElementById('info').textContent = text; +} + +function makeStep() { + const cell = robot.currentCell; + if (!cell) return; + + if (cell.type === CellType.FINISH) { + updateInfo('Работа завершена'); + isWorking = false; + document.getElementById('startBtn').disabled = false; + drawGrid(); + return; + } + + if (cell.type === CellType.FLOOR) { + robot.floor(); + updateInfo(`Позиция (${robot.currentCell.x}, ${robot.currentCell.y}): Пол → Панель`); + drawGrid(); + return; + } else if (cell.type === CellType.PANEL) { + robot.panel(); + updateInfo(`Позиция (${robot.currentCell.x}, ${robot.currentCell.y}): Панель → Активно`); + drawGrid(); + return; + } + + const moved = robot.moveTowardsGoal(); + if (!moved) { + updateInfo('Нет доступного пути к целям'); + isWorking = false; + document.getElementById('startBtn').disabled = false; + } else { + updateInfo(`Движение к позиции (${robot.currentCell.x}, ${robot.currentCell.y})`); + } + drawGrid(); +} + +function startWork() { + isWorking = true; + document.getElementById('startBtn').disabled = true; + automaticWork(); +} + +function automaticWork() { + if (!isWorking) { + document.getElementById('startBtn').disabled = false; + return; + } + + const cell = robot.currentCell; + + if (cell && cell.type === CellType.FINISH) { + updateInfo('Работа завершена'); + isWorking = false; + document.getElementById('startBtn').disabled = false; + return; + } + + makeStep(); + + if (isWorking) { + setTimeout(automaticWork, 200); + } +} + +function resetGame() { + isWorking = false; + document.getElementById('startBtn').disabled = false; + + const cellValues = createTestGrid(WIDTH, HEIGHT); + maze = new OperatorRobotMaze(WIDTH, HEIGHT, cellValues); + robot = new OperatorRobot(maze); + + drawGrid(); + updateInfo('Готов к работе'); +} + +const cellValues = createTestGrid(WIDTH, HEIGHT); +maze = new OperatorRobotMaze(WIDTH, HEIGHT, cellValues); +robot = new OperatorRobot(maze); +drawGrid(); \ No newline at end of file diff --git "a/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/index.html" "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/index.html" new file mode 100644 index 0000000..e953afd --- /dev/null +++ "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/index.html" @@ -0,0 +1,58 @@ + + + + + + Робот Оператор - Панели + + + +
+

🤖 Робот Оператор

+ +
+ Готов к работе +
+ +
+
+
+ +
+ + + +
+ +
+
+
+ Пол (П) +
+
+
+ Панель (Пн) +
+
+
+ Активно (А) +
+
+
+ Стена (■) +
+
+
+ Блок (▓) +
+
+
+ Финиш (F) +
+
+
+ + + + + \ No newline at end of file diff --git "a/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/lab2.mp4" "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/lab2.mp4" new file mode 100644 index 0000000..16924b7 Binary files /dev/null and "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/lab2.mp4" differ diff --git "a/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/robot.js" "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/robot.js" new file mode 100644 index 0000000..401fdbc --- /dev/null +++ "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/robot.js" @@ -0,0 +1,253 @@ +const CellType = { + FLOOR: 0x0, + PANEL: 0x1, + ACTIVE: 0x2, + SLUICE: 0x3, + WALL: 0x4, + BLOCK: 0x5, + FINISH: 0x6 +}; + +class OperatorRobotCell { + constructor(x, y, cellValue = 0x0) { + this.x = x; + this.y = y; + this.hasRobot = (cellValue & 0x8) !== 0; + this.type = this.cellTypeFromValue(cellValue); + } + + cellTypeFromValue(value) { + const baseValue = value & 0x7; + return baseValue; + } +} + +class OperatorRobotMaze { + constructor(width = 0, height = 0, cells = null) { + this.width = width; + this.height = height; + this.cells = []; + + if (cells !== null) { + this.loadFromValues(cells); + } else if (width > 0 && height > 0) { + this.initializeMaze(); + } + } + + loadFromValues(cellValues) { + if (!cellValues || cellValues.length === 0) { + this.height = 0; + this.width = 0; + this.cells = []; + return; + } + + this.height = cellValues.length; + this.width = cellValues[0].length; + this.cells = []; + + for (let y = 0; y < this.height; y++) { + const row = []; + for (let x = 0; x < this.width; x++) { + const cellValue = cellValues[y][x]; + const cell = new OperatorRobotCell(x, y, cellValue); + row.push(cell); + } + this.cells.push(row); + } + } + + initializeMaze(cellType = CellType.FLOOR) { + this.cells = []; + for (let y = 0; y < this.height; y++) { + const row = []; + for (let x = 0; x < this.width; x++) { + const cell = new OperatorRobotCell(x, y, cellType); + row.push(cell); + } + this.cells.push(row); + } + } + + getCell(x, y) { + if (x >= 0 && x < this.width && y >= 0 && y < this.height) { + return this.cells[y][x]; + } + return null; + } + + isFree(x, y) { + if (x < 0 || x >= this.width || y < 0 || y >= this.height) return false; + const cell = this.getCell(x, y); + return cell && cell.type !== CellType.WALL && cell.type !== CellType.BLOCK; + } +} + +class OperatorRobot { + constructor(maze) { + this.maze = maze; + this.currentCell = null; + + if (maze && maze.cells) { + for (let y = 0; y < maze.height; y++) { + for (let x = 0; x < maze.width; x++) { + const cell = maze.getCell(x, y); + if (cell && cell.hasRobot) { + this.currentCell = cell; + return; + } + } + } + } + } + + moveToCell(newCell) { + if (newCell && newCell.type !== CellType.WALL && newCell.type !== CellType.BLOCK) { + if (this.currentCell) { + this.currentCell.hasRobot = false; + } + this.currentCell = newCell; + newCell.hasRobot = true; + return newCell; + } + return null; + } + + channelForward() { + if (this.maze && this.currentCell) { + const newCell = this.maze.getCell(this.currentCell.x, this.currentCell.y + 1); + return this.moveToCell(newCell); + } + return null; + } + + channelBackward() { + if (this.maze && this.currentCell) { + const newCell = this.maze.getCell(this.currentCell.x, this.currentCell.y - 1); + return this.moveToCell(newCell); + } + return null; + } + + channelLeft() { + if (this.maze && this.currentCell) { + const newCell = this.maze.getCell(this.currentCell.x - 1, this.currentCell.y); + return this.moveToCell(newCell); + } + return null; + } + + channelRight() { + if (this.maze && this.currentCell) { + const newCell = this.maze.getCell(this.currentCell.x + 1, this.currentCell.y); + return this.moveToCell(newCell); + } + return null; + } + + liftDiag() { + if (this.maze && this.currentCell) { + const newCell = this.maze.getCell(this.currentCell.x - 1, this.currentCell.y + 1); + return this.moveToCell(newCell); + } + return null; + } + + descendDiag() { + if (this.maze && this.currentCell) { + const newCell = this.maze.getCell(this.currentCell.x + 1, this.currentCell.y - 1); + return this.moveToCell(newCell); + } + return null; + } + + panel() { + if (this.currentCell && this.currentCell.type === CellType.PANEL) { + this.currentCell.type = CellType.ACTIVE; + } + } + + floor() { + if (this.currentCell && this.currentCell.type === CellType.FLOOR) { + this.currentCell.type = CellType.PANEL; + } + } + + findNearestTargets() { + const targets = []; + for (let y = 0; y < this.maze.height; y++) { + for (let x = 0; x < this.maze.width; x++) { + const cell = this.maze.getCell(x, y); + if (cell && (cell.type === CellType.FLOOR || cell.type === CellType.PANEL)) { + targets.push([x, y]); + } + } + } + if (targets.length > 0) return targets; + + for (let y = 0; y < this.maze.height; y++) { + for (let x = 0; x < this.maze.width; x++) { + const cell = this.maze.getCell(x, y); + if (cell && cell.type === CellType.FINISH) { + return [[x, y]]; + } + } + } + return []; + } + + bfsNextStep(start, goals) { + const goalsSet = new Set(goals.map(g => `${g[0]},${g[1]}`)); + const queue = [[start[0], start[1]]]; + const prev = { [`${start[0]},${start[1]}`]: null }; + + while (queue.length > 0) { + const [x, y] = queue.shift(); + const key = `${x},${y}`; + + if (goalsSet.has(key)) { + let cur = [x, y]; + let curKey = key; + const startKey = `${start[0]},${start[1]}`; + + while (prev[curKey] !== null) { + const prevPos = prev[curKey]; + const prevKey = `${prevPos[0]},${prevPos[1]}`; + if (prevKey === startKey) { + return cur; + } + cur = prevPos; + curKey = prevKey; + } + return null; + } + + const neighbors = [ + [x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1], + [x - 1, y + 1], [x + 1, y - 1] + ]; + + for (const [nx, ny] of neighbors) { + const nKey = `${nx},${ny}`; + if (!prev.hasOwnProperty(nKey) && this.maze.isFree(nx, ny)) { + prev[nKey] = [x, y]; + queue.push([nx, ny]); + } + } + } + return null; + } + + moveTowardsGoal() { + const goals = this.findNearestTargets(); + if (goals.length === 0) return false; + + const nextPos = this.bfsNextStep([this.currentCell.x, this.currentCell.y], goals); + if (!nextPos) return false; + + const [nx, ny] = nextPos; + const newCell = this.maze.getCell(nx, ny); + return this.moveToCell(newCell) !== null; + } +} \ No newline at end of file diff --git "a/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/robot_server.py " "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/robot_server.py " new file mode 100644 index 0000000..b61f565 --- /dev/null +++ "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/robot_server.py " @@ -0,0 +1,350 @@ +from enum import Enum +from typing import Optional, List, Tuple +from collections import deque +import json + + +class CellType(Enum): + FLOOR = 0x0 + PANEL = 0x1 + ACTIVE = 0x2 + SLUICE = 0x3 + WALL = 0x4 + BLOCK = 0x5 + FINISH = 0x6 + + +class OperatorRobotCell: + def __init__(self, x: int = 0, y: int = 0, cell_value: int = 0x0): + self.x = x + self.y = y + self.has_robot = (cell_value & 0x8) != 0 + self.cell_type = self._cell_type_from_value(cell_value) + + def _cell_type_from_value(self, value: int) -> CellType: + base_value = value & 0x7 + for cell_type in CellType: + if cell_type.value == base_value: + return cell_type + return CellType.FLOOR + + def to_dict(self): + return { + 'x': self.x, + 'y': self.y, + 'type': self.cell_type.value, + 'has_robot': self.has_robot + } + + +class OperatorRobotMaze: + def __init__(self, width: int = None, height: int = None, cells: List[List[int]] = None): + self.width = width if width is not None else 0 + self.height = height if height is not None else 0 + self.cells = [] + + if cells is not None: + self.load_from_values(cells) + elif width is not None and height is not None: + self.initialize_maze() + + def load_from_values(self, cell_values: List[List[int]]): + if not cell_values: + self.height = 0 + self.width = 0 + self.cells = [] + return + + self.height = len(cell_values) + self.width = len(cell_values[0]) if self.height > 0 else 0 + self.cells = [] + + for y in range(self.height): + row = [] + for x in range(self.width): + cell_value = cell_values[y][x] + cell = OperatorRobotCell(x, y, cell_value) + row.append(cell) + self.cells.append(row) + + def initialize_maze(self, cell_type: CellType = None): + if cell_type is None: + cell_type = CellType.FLOOR + + self.cells = [] + for y in range(self.height): + row = [] + for x in range(self.width): + cell_value = cell_type.value + cell = OperatorRobotCell(x, y, cell_value) + row.append(cell) + self.cells.append(row) + + def get_cell(self, x: int, y: int) -> Optional[OperatorRobotCell]: + if 0 <= x < self.width and 0 <= y < self.height: + return self.cells[y][x] + return None + + def is_free(self, x: int, y: int) -> bool: + if not (0 <= x < self.width and 0 <= y < self.height): + return False + cell = self.get_cell(x, y) + if not cell: + return False + return cell.cell_type not in (CellType.WALL, CellType.BLOCK) + + def to_dict(self): + return { + 'width': self.width, + 'height': self.height, + 'cells': [[cell.to_dict() for cell in row] for row in self.cells] + } + + +class OperatorRobot: + def __init__(self, maze: OperatorRobotMaze = None): + self.maze = maze + self.current_cell = None + + if maze and maze.cells: + for y in range(maze.height): + for x in range(maze.width): + cell = maze.get_cell(x, y) + if cell and cell.has_robot: + self.current_cell = cell + return + + def _move_to_cell(self, new_cell: Optional[OperatorRobotCell]) -> Optional[OperatorRobotCell]: + if new_cell and new_cell.cell_type not in [CellType.WALL, CellType.BLOCK]: + if self.current_cell: + self.current_cell.has_robot = False + self.current_cell = new_cell + new_cell.has_robot = True + return new_cell + return None + + def channel_forward(self) -> Optional[OperatorRobotCell]: + if self.maze and self.current_cell: + new_cell = self.maze.get_cell(self.current_cell.x, self.current_cell.y + 1) + return self._move_to_cell(new_cell) + return None + + def channel_backward(self) -> Optional[OperatorRobotCell]: + if self.maze and self.current_cell: + new_cell = self.maze.get_cell(self.current_cell.x, self.current_cell.y - 1) + return self._move_to_cell(new_cell) + return None + + def channel_left(self) -> Optional[OperatorRobotCell]: + if self.maze and self.current_cell: + new_cell = self.maze.get_cell(self.current_cell.x - 1, self.current_cell.y) + return self._move_to_cell(new_cell) + return None + + def channel_right(self) -> Optional[OperatorRobotCell]: + if self.maze and self.current_cell: + new_cell = self.maze.get_cell(self.current_cell.x + 1, self.current_cell.y) + return self._move_to_cell(new_cell) + return None + + def lift_diag(self) -> Optional[OperatorRobotCell]: + if self.maze and self.current_cell: + new_cell = self.maze.get_cell(self.current_cell.x - 1, self.current_cell.y + 1) + return self._move_to_cell(new_cell) + return None + + def descend_diag(self) -> Optional[OperatorRobotCell]: + if self.maze and self.current_cell: + new_cell = self.maze.get_cell(self.current_cell.x + 1, self.current_cell.y - 1) + return self._move_to_cell(new_cell) + return None + + def panel(self): + if self.current_cell and self.current_cell.cell_type == CellType.PANEL: + self.current_cell.cell_type = CellType.ACTIVE + + def floor(self): + if self.current_cell and self.current_cell.cell_type == CellType.FLOOR: + self.current_cell.cell_type = CellType.PANEL + + def find_nearest_targets(self) -> List[Tuple[int, int]]: + targets = [] + for y in range(self.maze.height): + for x in range(self.maze.width): + cell = self.maze.get_cell(x, y) + if cell and cell.cell_type in (CellType.FLOOR, CellType.PANEL): + targets.append((x, y)) + + if targets: + return targets + + for y in range(self.maze.height): + for x in range(self.maze.width): + cell = self.maze.get_cell(x, y) + if cell and cell.cell_type == CellType.FINISH: + return [(x, y)] + + return [] + + def bfs_next_step(self, start: Tuple[int, int], goals: List[Tuple[int, int]]) -> Optional[Tuple[int, int]]: + goals_set = set(goals) + queue = deque([start]) + prev = {start: None} + + while queue: + x, y = queue.popleft() + + if (x, y) in goals_set: + cur = (x, y) + while prev[cur] is not None and prev[cur] != start: + cur = prev[cur] + return cur if cur != start else None + + neighbors = [ + (x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1), + (x - 1, y + 1), (x + 1, y - 1) + ] + + for nx, ny in neighbors: + if (nx, ny) not in prev and self.maze.is_free(nx, ny): + prev[(nx, ny)] = (x, y) + queue.append((nx, ny)) + + return None + + def move_towards_goal(self) -> bool: + goals = self.find_nearest_targets() + if not goals: + return False + + next_pos = self.bfs_next_step((self.current_cell.x, self.current_cell.y), goals) + if not next_pos: + return False + + new_cell = self.maze.get_cell(next_pos[0], next_pos[1]) + return self._move_to_cell(new_cell) is not None + + def make_step(self) -> dict: + """Выполнить один шаг и вернуть результат""" + if not self.current_cell: + return {'status': 'error', 'message': 'Робот не найден'} + + cell = self.current_cell + + if cell.cell_type == CellType.FINISH: + return { + 'status': 'finished', + 'message': 'Работа завершена', + 'position': (cell.x, cell.y) + } + + if cell.cell_type == CellType.FLOOR: + self.floor() + return { + 'status': 'processing', + 'message': f'Позиция ({cell.x}, {cell.y}): Пол → Панель', + 'position': (cell.x, cell.y) + } + elif cell.cell_type == CellType.PANEL: + self.panel() + return { + 'status': 'processing', + 'message': f'Позиция ({cell.x}, {cell.y}): Панель → Активно', + 'position': (cell.x, cell.y) + } + + moved = self.move_towards_goal() + if not moved: + return { + 'status': 'error', + 'message': 'Нет доступного пути к целям' + } + + return { + 'status': 'moving', + 'message': f'Движение к позиции ({self.current_cell.x}, {self.current_cell.y})', + 'position': (self.current_cell.x, self.current_cell.y) + } + + def get_state(self) -> dict: + """Получить текущее состояние робота и лабиринта""" + return { + 'maze': self.maze.to_dict(), + 'robot_position': (self.current_cell.x, self.current_cell.y) if self.current_cell else None + } + + +def create_test_grid(width: int, height: int) -> List[List[int]]: + """Создать тестовую сетку""" + cell_values = [] + + for y in range(height): + row = [] + for x in range(width): + if x == 0 and y == 0: + cell_value = CellType.FLOOR.value | 0x8 # Пол + робот + elif x == width - 1 and y == height - 1: + cell_value = CellType.FINISH.value + elif (x == 2 and y == 1) or (x == 3 and y == 2): + cell_value = CellType.WALL.value + elif x == 1 and y == 2: + cell_value = CellType.BLOCK.value + elif (x + y) % 3 == 0: + cell_value = CellType.PANEL.value + else: + cell_value = CellType.FLOOR.value + + row.append(cell_value) + cell_values.append(row) + + return cell_values + + +# Пример использования с Flask +""" +from flask import Flask, jsonify, request +from flask_cors import CORS + +app = Flask(__name__) +CORS(app) + +# Глобальное состояние +game_state = None + +@app.route('/api/init', methods=['POST']) +def init_game(): + global game_state + data = request.json + width = data.get('width', 7) + height = data.get('height', 5) + + cell_values = create_test_grid(width, height) + maze = OperatorRobotMaze(width, height, cell_values) + robot = OperatorRobot(maze) + + game_state = {'maze': maze, 'robot': robot} + + return jsonify(robot.get_state()) + +@app.route('/api/step', methods=['POST']) +def make_step(): + if not game_state: + return jsonify({'error': 'Game not initialized'}), 400 + + result = game_state['robot'].make_step() + state = game_state['robot'].get_state() + + return jsonify({ + 'result': result, + 'state': state + }) + +@app.route('/api/state', methods=['GET']) +def get_state(): + if not game_state: + return jsonify({'error': 'Game not initialized'}), 400 + + return jsonify(game_state['robot'].get_state()) + +if __name__ == '__main__': + app.run(debug=True, port=5000) \ No newline at end of file diff --git "a/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/styles.css" "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/styles.css" new file mode 100644 index 0000000..a8ee65a --- /dev/null +++ "b/5030102_30201/\320\233\320\270\321\202\320\276\320\262\321\207\320\265\320\275\320\272\320\276/lab2/styles.css" @@ -0,0 +1,186 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; +} + +.container { + background: white; + border-radius: 20px; + padding: 30px; + box-shadow: 0 20px 60px rgba(0,0,0,0.3); + max-width: 900px; +} + +h1 { + text-align: center; + color: #667eea; + margin-bottom: 20px; + font-size: 2em; +} + +.info-panel { + background: #f8f9fa; + padding: 15px; + border-radius: 10px; + margin-bottom: 20px; + text-align: center; + font-size: 1.1em; + color: #333; + min-height: 50px; + display: flex; + align-items: center; + justify-content: center; +} + +.grid-container { + display: inline-block; + border: 3px solid #667eea; + border-radius: 10px; + overflow: hidden; + margin: 0 auto 20px; + display: block; + width: fit-content; + margin-left: auto; + margin-right: auto; +} + +.grid { + display: grid; + gap: 2px; + background: #ddd; + padding: 2px; +} + +.cell { + width: 70px; + height: 70px; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + font-size: 14px; + position: relative; + transition: all 0.3s ease; +} + +.cell.floor { background: white; color: #999; } +.cell.panel { background: #87ceeb; color: #333; } +.cell.active { background: #32cd32; color: white; } +.cell.sluice { background: #808080; color: white; } +.cell.wall { background: #000; color: white; } +.cell.block { background: #555; color: white; } +.cell.finish { background: #ffd700; color: #333; } + +.robot { + position: absolute; + width: 40px; + height: 40px; + background: #ff4444; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 18px; + font-weight: bold; + border: 3px solid #cc0000; + z-index: 10; + animation: pulse 1s infinite; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.1); } +} + +.controls { + display: flex; + gap: 15px; + justify-content: center; + flex-wrap: wrap; +} + +button { + padding: 12px 30px; + font-size: 16px; + border: none; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; + font-weight: bold; + text-transform: uppercase; +} + +button.start { + background: #32cd32; + color: white; +} + +button.start:hover { + background: #28a428; + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(50, 205, 50, 0.4); +} + +button.step { + background: #667eea; + color: white; +} + +button.step:hover { + background: #5568d3; + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); +} + +button.reset { + background: #ff6b6b; + color: white; +} + +button.reset:hover { + background: #ee5555; + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(255, 107, 107, 0.4); +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none !important; +} + +.legend { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 10px; + margin-top: 20px; + padding: 15px; + background: #f8f9fa; + border-radius: 10px; +} + +.legend-item { + display: flex; + align-items: center; + gap: 10px; + font-size: 14px; +} + +.legend-color { + width: 30px; + height: 30px; + border-radius: 5px; + border: 2px solid #ddd; +} \ No newline at end of file