diff --git "a/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/__pycache__/main.cpython-310.pyc" "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/__pycache__/main.cpython-310.pyc" new file mode 100644 index 0000000..ef52195 Binary files /dev/null and "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/__pycache__/main.cpython-310.pyc" differ diff --git "a/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/__pycache__/ui.cpython-310.pyc" "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/__pycache__/ui.cpython-310.pyc" new file mode 100644 index 0000000..f6e7fba Binary files /dev/null and "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/__pycache__/ui.cpython-310.pyc" differ diff --git "a/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/app.py" "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/app.py" new file mode 100644 index 0000000..6465ebf --- /dev/null +++ "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/app.py" @@ -0,0 +1,144 @@ +""" +HydroRobot Application Launcher +Запуск анимации обхода робота по карте +""" + +import random +from main import HydroRobot, HydroMaze, HydroNetCellType +from ui import launch_animation + + +def create_sample_maze() -> tuple: + """ + Create a sample maze with water, shore, and barriers + Создает пример лабиринта с водой, берегом и преградами + """ + maze = HydroMaze(width=12, height=10) + maze.InitializeMaze(HydroNetCellType.Water, 12, 10) + + # Add some shores + for x in range(2, 5): + maze.cells[5][x].cell_type = HydroNetCellType.Shore + + # Add barriers + maze.cells[3][6].cell_type = HydroNetCellType.Barrier + maze.cells[3][7].cell_type = HydroNetCellType.Barrier + + # Add finish point + maze.cells[9][11].cell_type = HydroNetCellType.Finish + + # Set robot starting position + maze.cells[0][0].is_robot_cell = True + + return maze + + +def generate_random_maze(width: int, height: int) -> HydroMaze: + """ + Generate a random maze with random obstacles + Генерирует случайный лабиринт с препятствиями + """ + maze = HydroMaze(width=width, height=height) + maze.InitializeMaze(HydroNetCellType.Water, width, height) + + # Add random barriers (about 15% of cells) + barrier_count = int(width * height * 0.15) + for _ in range(barrier_count): + x = random.randint(1, width - 2) # Exclude borders + y = random.randint(1, height - 2) + if not (x == 0 and y == 0): # Don't place barrier at start + maze.cells[y][x].cell_type = HydroNetCellType.Barrier + + # Add random shores (about 10% of cells) + shore_count = int(width * height * 0.10) + for _ in range(shore_count): + x = random.randint(0, width - 1) + y = random.randint(0, height - 1) + if maze.cells[y][x].cell_type == HydroNetCellType.Water: + maze.cells[y][x].cell_type = HydroNetCellType.Shore + + # Add finish point at random location + finish_x = width - 1 + finish_y = height - 1 + maze.cells[finish_y][finish_x].cell_type = HydroNetCellType.Finish + + # Set robot starting position + maze.cells[0][0].is_robot_cell = True + + return maze + + +def get_maze_size() -> tuple: + """ + Get maze dimensions from user input + Получить размеры лабиринта от пользователя + """ + print("\nEnter maze dimensions:") + while True: + try: + width = int(input("Width (min 5, max 30): ")) + height = int(input("Height (min 5, max 30): ")) + + if 5 <= width <= 30 and 5 <= height <= 30: + return width, height + else: + print("Invalid dimensions. Please enter values between 5 and 30.") + except ValueError: + print("Invalid input. Please enter numeric values.") + + +def get_maze_type() -> str: + """ + Choose between sample or random maze + Выбрать между примером и случайной картой + """ + print("\nChoose maze type:") + print("1 - Sample maze (predefined)") + print("2 - Random maze (generated)") + + while True: + choice = input("Select (1 or 2): ").strip() + if choice in ['1', '2']: + return choice + print("Invalid choice. Please enter 1 or 2.") + + +def main(): + """Main application entry point""" + print("=" * 50) + print("HydroRobot Path Animation") + print("=" * 50) + + # Choose maze type + maze_type = get_maze_type() + + if maze_type == '1': + print("\nCreating sample maze...") + maze = create_sample_maze() + else: + # Get dimensions for random maze + width, height = get_maze_size() + print(f"\nGenerating random maze {width}x{height}...") + maze = generate_random_maze(width, height) + + # Create robot + robot = HydroRobot(maze) + + print("Maze created successfully!") + print(f"Maze size: {maze.width} x {maze.height}") + print(f"Robot starting position: (0, 0)") + print("\nControls:") + print(" Play/Pause - Start or pause the animation") + print(" Reset - Reset robot to start position") + print(" Space - Toggle play/pause") + print(" ESC - Exit") + print("\n" + "=" * 50) + print("Launching animation...") + print("=" * 50 + "\n") + + # Launch the animation + launch_animation(robot, maze) + + +if __name__ == "__main__": + main() diff --git "a/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/main.py" "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/main.py" new file mode 100644 index 0000000..758a0c7 --- /dev/null +++ "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/main.py" @@ -0,0 +1,282 @@ +from __future__ import annotations + +from enum import Enum +from typing import List, Iterator, Optional + + +# ========================= +# Enums +# ========================= + +class HydroNetCellType(Enum): + Water = 0 + Shore = 1 + Sample = 2 + Network = 3 + Barrier = 4 + Finish = 5 + Channel = 6 + + +class HydroMovementType(Enum): + SwimForward = 0 + SwimBackward = 1 + MoveLeft = 2 + MoveRight = 3 + DiagUpLeft = 4 + DiagDownRight = 5 + + +class BasicDirectionType(Enum): + North = 0 + South = 1 + West = 2 + East = 3 + NorthWest = 4 + SouthEast = 5 + + +# ========================= +# Direction Mappings +# ========================= + +SIDE_TO_DIRECTION = { + BasicDirectionType.North: HydroMovementType.SwimForward, + BasicDirectionType.South: HydroMovementType.SwimBackward, + BasicDirectionType.West: HydroMovementType.MoveLeft, + BasicDirectionType.East: HydroMovementType.MoveRight, + BasicDirectionType.NorthWest: HydroMovementType.DiagUpLeft, + BasicDirectionType.SouthEast: HydroMovementType.DiagDownRight, +} + +DIRECTION_TO_OFFSET = { + HydroMovementType.SwimForward: (0, 1), + HydroMovementType.SwimBackward: (0, -1), + HydroMovementType.MoveLeft: (-1, 0), + HydroMovementType.MoveRight: (1, 0), + HydroMovementType.DiagUpLeft: (-1, 1), + HydroMovementType.DiagDownRight: (1, -1), +} + +# ========================= +# Cell +# ========================= + +class HydroCell: + def __init__( + self, + cell_type: Optional[HydroNetCellType] = None, + is_robot_cell: bool = False, + x: int = 0, + y: int = 0, + ) -> None: + self.cell_type = cell_type + self.is_robot_cell = is_robot_cell + self.x = x + self.y = y + + +# ========================= +# Maze +# ========================= + +class HydroMaze: + def __init__( + self, + width: int = 0, + height: int = 0, + cells: Optional[List[List[HydroCell]]] = None, + ) -> None: + self.width = width + self.height = height + self.cells = cells or [] + + def _set_coordinates(self) -> None: + for y, row in enumerate(self.cells): + for x, cell in enumerate(row): + cell.x = x + cell.y = y + + def GetNeighborCell( + self, + current_cell: HydroCell, + search_direction: HydroMovementType, + ) -> Optional[HydroCell]: + dx, dy = DIRECTION_TO_OFFSET[search_direction] + nx = current_cell.x + dx + ny = current_cell.y + dy + + if not (0 <= nx < self.width and 0 <= ny < self.height): + return None + + neighbor = self.cells[ny][nx] + if neighbor.cell_type in ( + HydroNetCellType.Barrier, + HydroNetCellType.Channel, + ): + return None + + return neighbor + + def GetIterator(self) -> Iterator[HydroCell]: + if not self.cells: + return iter(()) + + self._set_coordinates() + + x = 0 + y = 0 + direction = 1 # 1 = right, -1 = left + + while True: + cell = self.cells[y][x] + # Skip barriers and channels - don't yield them + if cell.cell_type not in ( + HydroNetCellType.Barrier, + HydroNetCellType.Channel, + ): + yield cell + + if direction == 1: + if x == self.width - 1: + if y == self.height - 1: + break + y += 1 + direction = -1 + else: + x += 1 + else: + if x == 0: + if y == self.height - 1: + break + y += 1 + direction = 1 + else: + x -= 1 + + def GetSmartIterator(self) -> Iterator[HydroCell]: + """ + Get an iterator that avoids barriers and channels using BFS + Получить итератор, который избегает преград и каналов, используя BFS + """ + if not self.cells: + return iter(()) + + self._set_coordinates() + + visited = set() + queue = [(0, 0)] + visited.add((0, 0)) + + while queue: + x, y = queue.pop(0) + cell = self.cells[y][x] + yield cell + + # Try all 4 directions (up, down, left, right) + for dx, dy in [(0, 1), (0, -1), (-1, 0), (1, 0)]: + nx, ny = x + dx, y + dy + + # Check bounds + if not (0 <= nx < self.width and 0 <= ny < self.height): + continue + + # Skip if already visited + if (nx, ny) in visited: + continue + + neighbor = self.cells[ny][nx] + # Skip barriers and channels + if neighbor.cell_type in ( + HydroNetCellType.Barrier, + HydroNetCellType.Channel, + ): + continue + + visited.add((nx, ny)) + queue.append((nx, ny)) + + def InitializeMaze( + self, + cell_type: HydroNetCellType, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> None: + if width is not None: + self.width = width + if height is not None: + self.height = height + + self.cells = [] + for y in range(self.height): + row = [] + for x in range(self.width): + row.append(HydroCell(cell_type, False, x, y)) + self.cells.append(row) + + +# ========================= +# Robot +# ========================= + +class HydroRobot: + def __init__(self, maze: Optional[HydroMaze] = None) -> None: + self.maze = maze + self._current_cell: Optional[HydroCell] = None + + @property + def current_cell(self) -> Optional[HydroCell]: + if self._current_cell is None and self.maze: + for row in self.maze.cells: + for cell in row: + if cell.is_robot_cell: + self._current_cell = cell + return cell + return self._current_cell + + def _set_current_cell( + self, cell: Optional[HydroCell] + ) -> Optional[HydroCell]: + if cell is None: + return None + if self._current_cell: + self._current_cell.is_robot_cell = False + self._current_cell = cell + cell.is_robot_cell = True + return cell + + def _move(self, direction: HydroMovementType) -> Optional[HydroCell]: + if not self.maze or not self.current_cell: + return None + next_cell = self.maze.GetNeighborCell( + self.current_cell, direction + ) + return self._set_current_cell(next_cell) + + # Movement methods + def SwimForward(self): + return self._move(HydroMovementType.SwimForward) + + def MoveBackward(self): + return self._move(HydroMovementType.SwimBackward) + + def MoveLeft(self): + return self._move(HydroMovementType.MoveLeft) + + def MoveRight(self): + return self._move(HydroMovementType.MoveRight) + + def MoveUp(self): + return self._move(HydroMovementType.DiagUpLeft) + + def MoveDown(self): + return self._move(HydroMovementType.DiagDownRight) + + # Specializations + def Water(self) -> None: + if self.current_cell and self.current_cell.cell_type == HydroNetCellType.Water: + self.current_cell.cell_type = HydroNetCellType.Sample + + def Shore(self) -> None: + if self.current_cell and self.current_cell.cell_type == HydroNetCellType.Shore: + self.current_cell.cell_type = HydroNetCellType.Water \ No newline at end of file diff --git "a/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/mapping.yaml" "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/mapping.yaml" new file mode 100644 index 0000000..ed2511e --- /dev/null +++ "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/mapping.yaml" @@ -0,0 +1,57 @@ +классы: + - РоботГидролог: HydroRobot + свойства: + - лабиринт: maze + методы: + - ПлытьВперёд: SwimForward + - ОтойтиВзад: MoveBackward + - СместитьсяВлево: MoveLeft + - СместитьсяВправо: MoveRight + - Подняться: MoveUp + - Спуститься: MoveDown + - Вода: Water + - Берег: Shore + + - ЛабиринтРоботГидролог: HydroMaze + свойства: + - ширина: width + - длина: height + - ячейки: cells + методы: + - ПолучитьСоседнююЯчейку: GetNeighborCell + - ПолучитьИтератор: GetIterator + - ИнициализироватьЛабиринт: InitializeMaze + + - ЯчейкаРоботГидролог: HydroCell + свойства: + - ячейка_робота: is_robot_cell + - тип_ячейки: cell_type + +перечисления: + - ТипЯчеекГидросети: HydroNetCellType + опции: + - Вода: Water + - Берег: Shore + - Образец: Sample + - Сеть: Network + - Преграда: Barrier + - Финиш: Finish + - Канал: Channel + + - ТипНаправленийГидрНап: HydroMovementType + опции: + - ПлытьВперёд: SwimForward + - ПлытьНазад: SwimBackward + - СместитьсяВлево: MoveLeft + - СместитьсяВправо: MoveRight + - ДиагГВ: DiagUpLeft + - ДиагГН: DiagDownRight + + - БазовыеТипыНаправления: BasicDirectionType + опции: + - С: North + - Ю: South + - З: West + - В: East + - СЗ: NorthWest + - ЮВ: SouthEast \ No newline at end of file diff --git "a/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/record_stage1.mkv" "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/record_stage1.mkv" new file mode 100644 index 0000000..e263e08 Binary files /dev/null and "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/record_stage1.mkv" differ diff --git "a/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/ui.py" "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/ui.py" new file mode 100644 index 0000000..d6237d3 --- /dev/null +++ "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/desktop/ui.py" @@ -0,0 +1,541 @@ +import pygame +import sys +from typing import Optional, Tuple, List +from main import ( + HydroRobot, + HydroMaze, + HydroNetCellType, + HydroCell, +) + + +# Colors +COLOR_WATER = (70, 130, 180) # Steel blue +COLOR_SHORE = (210, 180, 140) # Tan +COLOR_SAMPLE = (255, 140, 0) # Dark orange +COLOR_NETWORK = (34, 139, 34) # Forest green +COLOR_BARRIER = (139, 69, 19) # Saddle brown +COLOR_FINISH = (50, 205, 50) # Lime green +COLOR_CHANNEL = (0, 0, 0) # Black +COLOR_BACKGROUND = (240, 240, 240) # Light gray +COLOR_GRID = (180, 180, 180) # Medium gray +COLOR_ROBOT = (255, 0, 0) # Red +COLOR_VISITED = (173, 216, 230) # Light blue +COLOR_TEXT = (0, 0, 0) # Black +COLOR_BUTTON = (100, 149, 237) # Cornflower blue +COLOR_BUTTON_HOVER = (65, 105, 225) # Royal blue + +# Cell type to color mapping +CELL_TYPE_COLORS = { + HydroNetCellType.Water: COLOR_WATER, + HydroNetCellType.Shore: COLOR_SHORE, + HydroNetCellType.Sample: COLOR_SAMPLE, + HydroNetCellType.Network: COLOR_NETWORK, + HydroNetCellType.Barrier: COLOR_BARRIER, + HydroNetCellType.Finish: COLOR_FINISH, + HydroNetCellType.Channel: COLOR_CHANNEL, +} + + +class ControlButton: + def __init__( + self, x: int, y: int, width: int, height: int, text: str, action=None + ): + self.rect = pygame.Rect(x, y, width, height) + self.text = text + self.action = action + self.is_hovered = False + + def draw(self, surface: pygame.Surface, font: pygame.font.Font): + color = COLOR_BUTTON_HOVER if self.is_hovered else COLOR_BUTTON + + pygame.draw.rect(surface, color, self.rect) + pygame.draw.rect(surface, COLOR_TEXT, self.rect, 2) + + text_surface = font.render(self.text, True, (255, 255, 255)) + text_rect = text_surface.get_rect(center=self.rect.center) + surface.blit(text_surface, text_rect) + + def update(self, mouse_pos: Tuple[int, int]): + self.is_hovered = self.rect.collidepoint(mouse_pos) + + def handle_click(self) -> bool: + if self.is_hovered and self.action: + self.action() + return True + return False + + +class HydroRobotAnimation: + def __init__( + self, + robot: HydroRobot, + maze: HydroMaze, + cell_size: int = 50, + margin: int = 50, + ): + pygame.init() + + self.robot = robot + self.maze = maze + self.cell_size = cell_size + self.margin = margin + + # Calculate window size + self.width = maze.width * cell_size + 2 * margin + 200 + self.height = maze.height * cell_size + 2 * margin + 100 + + self.screen = pygame.display.set_mode((self.width, self.height)) + pygame.display.set_caption("HydroRobot Path Animation") + + self.font_large = pygame.font.Font(None, 36) + self.font_medium = pygame.font.Font(None, 24) + self.font_small = pygame.font.Font(None, 18) + + self.clock = pygame.time.Clock() + self.fps = 60 + + # Animation parameters + self.animation_speed = 0.5 # seconds per cell + self.move_elapsed_time = 0 + self.is_moving = False + self.move_start_pos = None + self.move_end_pos = None + + # Animation state + self.path_iterator = None + self.visited_cells = set() + self.current_step = 0 + self.total_steps = 0 + self.cell_transformations = {} # Track what each visited cell transformed to + + # Multi-step transformation state + self.current_cell_transformations = [] # Queue of transformations for current cell + self.transformation_index = 0 # Which transformation we're on + self.transformation_delay = 0.3 # seconds to wait between transformations + self.transformation_timer = 0 # timer for current transformation + self.is_transforming = False # True when we're transforming, False when moving + + # Control state + self.is_playing = False + self.is_paused = False + self.is_finished = False + self.current_message = "" + self.message_time = 0 + self.message_duration = 2000 # ms + + # Buttons + self.buttons = self._create_buttons() + + self.is_running = True + + def _create_buttons(self) -> List[ControlButton]: + maze_area_width = self.maze.width * self.cell_size + 2 * self.margin + button_x = maze_area_width + 20 + button_y = self.margin + 50 + button_width = 180 + button_height = 40 + button_spacing = 50 + + buttons = [ + ControlButton( + button_x, button_y, button_width, button_height, "Play", self.play + ), + ControlButton( + button_x, + button_y + button_spacing, + button_width, + button_height, + "Pause", + self.pause, + ), + ControlButton( + button_x, + button_y + button_spacing * 2, + button_width, + button_height, + "Reset", + self.reset, + ), + ] + + return buttons + + def _init_animation(self): + """Initialize the animation by getting the path iterator""" + self.path_iterator = self.maze.GetIterator() + self.visited_cells = set() + self.current_step = 0 + self.transformation_index = 0 + self.transformation_timer = 0 + + # Count total reachable cells (avoiding barriers and channels) + temp_iterator = self.maze.GetIterator() + self.total_steps = sum(1 for _ in temp_iterator) + + # Get first cell + if self.robot.current_cell: + self.visited_cells.add( + (self.robot.current_cell.x, self.robot.current_cell.y) + ) + self.current_step = 1 + + def play(self): + if not self.is_playing: + if self.is_finished: + self.reset() + if self.path_iterator is None: + self._init_animation() + self.is_playing = True + self.is_paused = False + + def pause(self): + self.is_playing = False + self.is_paused = True + + def reset(self): + # Reset robot to start position + for row in self.maze.cells: + for cell in row: + cell.is_robot_cell = False + self.maze.cells[0][0].is_robot_cell = True + + self.robot._current_cell = None + self.path_iterator = None + self.visited_cells = set() + self.cell_transformations = {} + self.current_cell_transformations = [] + self.transformation_index = 0 + self.transformation_timer = 0 + self.is_transforming = False + self.current_step = 0 + self.is_playing = False + self.is_paused = False + self.is_finished = False + self.is_moving = False + self.current_message = "" + + def _get_cell_screen_pos(self, x: int, y: int) -> Tuple[int, int]: + """Convert maze coordinates to screen coordinates""" + screen_x = self.margin + x * self.cell_size + screen_y = self.margin + (self.maze.height - 1 - y) * self.cell_size + return screen_x, screen_y + + def _move_to_next_cell(self): + """Move robot to the next cell in the path""" + if self.path_iterator is None: + return + + try: + next_cell = next(self.path_iterator) + + self.move_start_pos = ( + self.robot.current_cell.x, + self.robot.current_cell.y, + ) + self.robot._current_cell = next_cell + self.move_end_pos = (next_cell.x, next_cell.y) + + cell_coords = (next_cell.x, next_cell.y) + self.visited_cells.add(cell_coords) + + # Just move to the cell, don't transform yet + self.is_moving = True + self.is_transforming = False + self.move_elapsed_time = 0 + + except StopIteration: + self.is_playing = False + self.is_finished = True + self.current_message = "Animation finished! All cells processed!" + self.message_time = pygame.time.get_ticks() + + def _start_transformations(self): + """Start transformations on current cell after movement""" + if not self.robot.current_cell: + return + + # Determine transformations needed for this cell + self.current_cell_transformations = [] + + if self.robot.current_cell.cell_type == HydroNetCellType.Water: + # Water needs 1 transformation: Water -> Sample + self.current_cell_transformations = [ + (HydroNetCellType.Sample, "Water -> Sample") + ] + elif self.robot.current_cell.cell_type == HydroNetCellType.Shore: + # Shore needs 2 transformations: Shore -> Water -> Sample + self.current_cell_transformations = [ + (HydroNetCellType.Water, "Shore -> Water"), + (HydroNetCellType.Sample, "Water -> Sample") + ] + + self.transformation_index = 0 + self.transformation_timer = 0 + self.is_transforming = True + + def _apply_transformations(self, dt: float): + """Apply transformations to current cell""" + if not self.is_transforming or not self.current_cell_transformations: + return + + self.transformation_timer += dt + + # Check if it's time to apply next transformation + if self.transformation_timer >= self.transformation_delay: + if self.transformation_index < len(self.current_cell_transformations): + target_type, message = self.current_cell_transformations[self.transformation_index] + + # Apply transformation + if self.robot.current_cell: + self.robot.current_cell.cell_type = target_type + cell_coords = (self.robot.current_cell.x, self.robot.current_cell.y) + self.cell_transformations[cell_coords] = target_type + self.current_message = f"{message} at ({self.robot.current_cell.x}, {self.robot.current_cell.y})" + self.message_time = pygame.time.get_ticks() + + self.transformation_index += 1 + self.transformation_timer = 0 + else: + # All transformations done for this cell + self.is_transforming = False + self.current_cell_transformations = [] + + def _draw_maze(self): + # Draw grid background + for y in range(self.maze.height): + for x in range(self.maze.width): + cell = self.maze.cells[y][x] + screen_x, screen_y = self._get_cell_screen_pos(x, y) + + # Determine color based on transformation + cell_coords = (x, y) + if cell_coords in self.cell_transformations: + # Show the transformed cell type + transformed_type = self.cell_transformations[cell_coords] + color = CELL_TYPE_COLORS.get(transformed_type, COLOR_WATER) + else: + # Show original cell type + color = CELL_TYPE_COLORS.get(cell.cell_type, COLOR_WATER) + + pygame.draw.rect( + self.screen, color, (screen_x, screen_y, self.cell_size, self.cell_size) + ) + pygame.draw.rect( + self.screen, + COLOR_GRID, + (screen_x, screen_y, self.cell_size, self.cell_size), + 1, + ) + + def _draw_robot(self): + if self.robot.current_cell is None: + return + + if self.is_moving and self.move_start_pos and self.move_end_pos: + # Animate robot movement + progress = min( + 1.0, + self.move_elapsed_time / (self.animation_speed * 1000), + ) + + start_screen = self._get_cell_screen_pos( + self.move_start_pos[0], self.move_start_pos[1] + ) + end_screen = self._get_cell_screen_pos( + self.move_end_pos[0], self.move_end_pos[1] + ) + + robot_x = start_screen[0] + (end_screen[0] - start_screen[0]) * progress + robot_y = start_screen[1] + (end_screen[1] - start_screen[1]) * progress + + if progress >= 1.0: + self.is_moving = False + else: + x, y = self.robot.current_cell.x, self.robot.current_cell.y + robot_x, robot_y = self._get_cell_screen_pos(x, y) + + # Draw robot as circle in the center of cell + center_x = int(robot_x + self.cell_size // 2) + center_y = int(robot_y + self.cell_size // 2) + radius = self.cell_size // 3 + + pygame.draw.circle(self.screen, COLOR_ROBOT, (center_x, center_y), radius) + pygame.draw.circle( + self.screen, COLOR_TEXT, (center_x, center_y), radius, 2 + ) + + def _draw_ui(self): + # Draw title + title = self.font_large.render("HydroRobot Path Animation", True, COLOR_TEXT) + self.screen.blit(title, (self.margin, 10)) + + # Draw maze info + maze_area_width = self.maze.width * self.cell_size + 2 * self.margin + info_x = maze_area_width + 20 + info_y = self.margin + 200 + + if self.robot.current_cell: + pos_text = self.font_small.render( + f"Position: ({self.robot.current_cell.x}, {self.robot.current_cell.y})", + True, + COLOR_TEXT, + ) + step_text = self.font_small.render( + f"Step: {self.current_step} / {self.total_steps}", + True, + COLOR_TEXT, + ) + cell_type_text = self.font_small.render( + f"Cell Type: {self.robot.current_cell.cell_type.name}", + True, + COLOR_TEXT, + ) + + self.screen.blit(pos_text, (info_x, info_y)) + self.screen.blit(step_text, (info_x, info_y + 25)) + self.screen.blit(cell_type_text, (info_x, info_y + 50)) + + # Draw status + status_y = info_y + 100 + if self.is_playing: + status = "Status: Playing" + elif self.is_paused: + status = "Status: Paused" + elif self.is_finished: + status = "Status: Finished" + else: + status = "Status: Stopped" + + status_text = self.font_small.render(status, True, COLOR_TEXT) + self.screen.blit(status_text, (info_x, status_y)) + + # Draw message + if self.current_message: + elapsed = pygame.time.get_ticks() - self.message_time + if elapsed < self.message_duration: + msg_text = self.font_medium.render( + self.current_message, True, COLOR_TEXT + ) + self.screen.blit(msg_text, (info_x, status_y + 50)) + + def _draw_controls(self): + for button in self.buttons: + button.draw(self.screen, self.font_small) + + def _draw_legend(self): + maze_area_width = self.maze.width * self.cell_size + 2 * self.margin + legend_x = maze_area_width + 20 + legend_y = self.height - 120 + + legend_title = self.font_medium.render("Legend:", True, COLOR_TEXT) + self.screen.blit(legend_title, (legend_x, legend_y)) + + cell_types = [ + (HydroNetCellType.Water, "Water"), + (HydroNetCellType.Shore, "Shore"), + (HydroNetCellType.Sample, "Sample"), + ] + + for i, (cell_type, name) in enumerate(cell_types): + color = CELL_TYPE_COLORS[cell_type] + y_pos = legend_y + 30 + i * 18 + + pygame.draw.rect(self.screen, color, (legend_x, y_pos, 15, 15)) + pygame.draw.rect(self.screen, COLOR_TEXT, (legend_x, y_pos, 15, 15), 1) + + text = self.font_small.render(name, True, COLOR_TEXT) + self.screen.blit(text, (legend_x + 20, y_pos)) + + # Visited cell legend + pygame.draw.rect(self.screen, COLOR_VISITED, (legend_x, legend_y + 84, 15, 15)) + pygame.draw.rect(self.screen, COLOR_TEXT, (legend_x, legend_y + 84, 15, 15), 1) + text = self.font_small.render("Visited", True, COLOR_TEXT) + self.screen.blit(text, (legend_x + 20, legend_y + 84)) + + def _handle_events(self): + mouse_pos = pygame.mouse.get_pos() + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self.is_running = False + + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + self.is_running = False + elif event.key == pygame.K_SPACE: + if self.is_playing: + self.pause() + else: + self.play() + + elif event.type == pygame.MOUSEBUTTONDOWN: + for button in self.buttons: + button.handle_click() + + # Update button hover states + for button in self.buttons: + button.update(mouse_pos) + + def _update(self, dt: float): + if self.is_playing: + if self.is_moving: + # Update movement animation + self.move_elapsed_time += dt * 1000 + # Check if movement is complete + if self.move_elapsed_time >= (self.animation_speed * 1000): + self.is_moving = False + self.move_elapsed_time = 0 + # Now start transformations + self._start_transformations() + elif self.is_transforming: + # Apply transformations + self._apply_transformations(dt) + else: + # No more transformations, move to next cell + self.current_step += 1 + self._move_to_next_cell() + + def run(self): + while self.is_running: + dt = self.clock.tick(self.fps) / 1000.0 + + self._handle_events() + self._update(dt) + + # Draw everything + self.screen.fill(COLOR_BACKGROUND) + + self._draw_maze() + self._draw_robot() + self._draw_ui() + self._draw_controls() + self._draw_legend() + + pygame.display.flip() + + pygame.quit() + sys.exit() + + +def launch_animation(robot: HydroRobot, maze: HydroMaze): + """Launch the HydroRobot animation""" + animation = HydroRobotAnimation(robot, maze) + animation.run() + + +if __name__ == "__main__": + from main import HydroRobot, HydroMaze, HydroNetCellType + + # Create a maze + maze = HydroMaze(width=10, height=10) + maze.InitializeMaze(HydroNetCellType.Water, 10, 10) + + # Set robot starting position + maze.cells[0][0].is_robot_cell = True + + # Create robot + robot = HydroRobot(maze) + + # Launch animation + launch_animation(robot, maze) diff --git "a/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/__pycache__/main.cpython-310.pyc" "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/__pycache__/main.cpython-310.pyc" new file mode 100644 index 0000000..4f976b7 Binary files /dev/null and "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/__pycache__/main.cpython-310.pyc" differ diff --git "a/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/main.py" "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/main.py" new file mode 100644 index 0000000..d4f721a --- /dev/null +++ "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/main.py" @@ -0,0 +1,323 @@ +from __future__ import annotations + +from enum import Enum +from typing import List, Iterator, Optional + + +# ========================= +# Enums +# ========================= + +class HydroNetCellType(Enum): + Water = 0 + Shore = 1 + Sample = 2 + Network = 3 + Barrier = 4 + Finish = 5 + Channel = 6 + + +class HydroMovementType(Enum): + SwimForward = 0 + SwimBackward = 1 + MoveLeft = 2 + MoveRight = 3 + DiagUpLeft = 4 + DiagDownRight = 5 + + +class BasicDirectionType(Enum): + North = 0 + South = 1 + West = 2 + East = 3 + NorthWest = 4 + SouthEast = 5 + + +# ========================= +# Direction Mappings +# ========================= + +SIDE_TO_DIRECTION = { + BasicDirectionType.North: HydroMovementType.SwimForward, + BasicDirectionType.South: HydroMovementType.SwimBackward, + BasicDirectionType.West: HydroMovementType.MoveLeft, + BasicDirectionType.East: HydroMovementType.MoveRight, + BasicDirectionType.NorthWest: HydroMovementType.DiagUpLeft, + BasicDirectionType.SouthEast: HydroMovementType.DiagDownRight, +} + +DIRECTION_TO_OFFSET = { + HydroMovementType.SwimForward: (0, 1), + HydroMovementType.SwimBackward: (0, -1), + HydroMovementType.MoveLeft: (-1, 0), + HydroMovementType.MoveRight: (1, 0), + HydroMovementType.DiagUpLeft: (-1, 1), + HydroMovementType.DiagDownRight: (1, -1), +} + +# ========================= +# Cell +# ========================= + +class HydroCell: + def __init__( + self, + cell_type: Optional[HydroNetCellType] = None, + is_robot_cell: bool = False, + x: int = 0, + y: int = 0, + ) -> None: + self.cell_type = cell_type + self.is_robot_cell = is_robot_cell + self.x = x + self.y = y + + +# ========================= +# Maze +# ========================= + +class HydroMaze: + def __init__( + self, + width: int = 0, + height: int = 0, + cells: Optional[List[List[HydroCell]]] = None, + ) -> None: + self.width = width + self.height = height + self.cells = cells or [] + + def _set_coordinates(self) -> None: + for y, row in enumerate(self.cells): + for x, cell in enumerate(row): + cell.x = x + cell.y = y + + def GetNeighborCell( + self, + current_cell: HydroCell, + search_direction: HydroMovementType, + ) -> Optional[HydroCell]: + dx, dy = DIRECTION_TO_OFFSET[search_direction] + nx = current_cell.x + dx + ny = current_cell.y + dy + + if not (0 <= nx < self.width and 0 <= ny < self.height): + return None + + neighbor = self.cells[ny][nx] + if neighbor.cell_type in ( + HydroNetCellType.Barrier, + HydroNetCellType.Channel, + ): + return None + + return neighbor + + def GetIterator(self) -> Iterator[HydroCell]: + if not self.cells: + return iter(()) + + self._set_coordinates() + + x = 0 + y = 0 + direction = 1 # 1 = right, -1 = left + + while True: + cell = self.cells[y][x] + # Skip barriers and channels - don't yield them + if cell.cell_type not in ( + HydroNetCellType.Barrier, + HydroNetCellType.Channel, + ): + yield cell + + if direction == 1: + if x == self.width - 1: + if y == self.height - 1: + break + y += 1 + direction = -1 + else: + x += 1 + else: + if x == 0: + if y == self.height - 1: + break + y += 1 + direction = 1 + else: + x -= 1 + + def GetIteratorFromBottomLeft(self) -> Iterator[HydroCell]: + """ + Get an iterator starting from bottom-left corner moving to top-right + Получить итератор, начиная с левого нижнего угла к правому верхнему + """ + if not self.cells: + return iter(()) + + self._set_coordinates() + + x = 0 + y = self.height - 1 + direction = 1 # 1 = right, -1 = left + + while True: + cell = self.cells[y][x] + # Skip barriers and channels - don't yield them + if cell.cell_type not in ( + HydroNetCellType.Barrier, + HydroNetCellType.Channel, + ): + yield cell + + # Move right or left depending on direction + if direction == 1: # Moving right + if x == self.width - 1: # Reached right edge + if y == 0: # Also at top + break + y -= 1 # Move up + direction = -1 # Change to moving left + else: + x += 1 # Continue right + else: # Moving left + if x == 0: # Reached left edge + if y == 0: # Also at top + break + y -= 1 # Move up + direction = 1 # Change to moving right + else: + x -= 1 # Continue left + + def GetSmartIterator(self) -> Iterator[HydroCell]: + """ + Get an iterator that avoids barriers and channels using BFS + Получить итератор, который избегает преград и каналов, используя BFS + """ + if not self.cells: + return iter(()) + + self._set_coordinates() + + visited = set() + queue = [(0, 0)] + visited.add((0, 0)) + + while queue: + x, y = queue.pop(0) + cell = self.cells[y][x] + yield cell + + # Try all 4 directions (up, down, left, right) + for dx, dy in [(0, 1), (0, -1), (-1, 0), (1, 0)]: + nx, ny = x + dx, y + dy + + # Check bounds + if not (0 <= nx < self.width and 0 <= ny < self.height): + continue + + # Skip if already visited + if (nx, ny) in visited: + continue + + neighbor = self.cells[ny][nx] + # Skip barriers and channels + if neighbor.cell_type in ( + HydroNetCellType.Barrier, + HydroNetCellType.Channel, + ): + continue + + visited.add((nx, ny)) + queue.append((nx, ny)) + + def InitializeMaze( + self, + cell_type: HydroNetCellType, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> None: + if width is not None: + self.width = width + if height is not None: + self.height = height + + self.cells = [] + for y in range(self.height): + row = [] + for x in range(self.width): + row.append(HydroCell(cell_type, False, x, y)) + self.cells.append(row) + + +# ========================= +# Robot +# ========================= + +class HydroRobot: + def __init__(self, maze: Optional[HydroMaze] = None) -> None: + self.maze = maze + self._current_cell: Optional[HydroCell] = None + + @property + def current_cell(self) -> Optional[HydroCell]: + if self._current_cell is None and self.maze: + for row in self.maze.cells: + for cell in row: + if cell.is_robot_cell: + self._current_cell = cell + return cell + return self._current_cell + + def _set_current_cell( + self, cell: Optional[HydroCell] + ) -> Optional[HydroCell]: + if cell is None: + return None + if self._current_cell: + self._current_cell.is_robot_cell = False + self._current_cell = cell + cell.is_robot_cell = True + return cell + + def _move(self, direction: HydroMovementType) -> Optional[HydroCell]: + if not self.maze or not self.current_cell: + return None + next_cell = self.maze.GetNeighborCell( + self.current_cell, direction + ) + return self._set_current_cell(next_cell) + + # Movement methods + def SwimForward(self): + return self._move(HydroMovementType.SwimForward) + + def MoveBackward(self): + return self._move(HydroMovementType.SwimBackward) + + def MoveLeft(self): + return self._move(HydroMovementType.MoveLeft) + + def MoveRight(self): + return self._move(HydroMovementType.MoveRight) + + def MoveUp(self): + return self._move(HydroMovementType.DiagUpLeft) + + def MoveDown(self): + return self._move(HydroMovementType.DiagDownRight) + + # Specializations + def Water(self) -> None: + if self.current_cell and self.current_cell.cell_type == HydroNetCellType.Water: + self.current_cell.cell_type = HydroNetCellType.Sample + + def Shore(self) -> None: + if self.current_cell and self.current_cell.cell_type == HydroNetCellType.Shore: + self.current_cell.cell_type = HydroNetCellType.Water \ No newline at end of file diff --git "a/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/record_stage2.mkv" "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/record_stage2.mkv" new file mode 100644 index 0000000..2cbaf5f Binary files /dev/null and "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/record_stage2.mkv" differ diff --git "a/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/static/app.js" "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/static/app.js" new file mode 100644 index 0000000..dcd7331 --- /dev/null +++ "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/static/app.js" @@ -0,0 +1,386 @@ +// Application state +let state = { + maze: null, + robot: null, + iterator: null, + isRunning: false, + isPaused: false, + cellSize: 30, + canvas: null, + ctx: null, + robotPosition: { x: 0, y: 0 }, + currentCell: null, + animationState: 'idle', // idle, moving, transforming + moveProgress: 0, // 0-1 + transformProgress: 0, // 0-1 + transformationQueue: [], + transformationTimer: 0, + MOVE_DURATION: 0.5, // seconds + TRANSFORM_DURATION: 0.3, // seconds per transformation + cellTransformations: {}, // tracks transformations applied to each cell + currentStep: 0, + totalSteps: 0, + lastFrameTime: Date.now(), +}; + +// Cell type enum +const CellType = { + 'Water': 0, + 'Shore': 1, + 'Sample': 2, + 'Network': 3, + 'Barrier': 4, + 'Finish': 5, + 'Channel': 6 +}; + +// Cell colors +const CellColors = { + 'Water': '#4682b4', + 'Shore': '#d2b48c', + 'Sample': '#ff8c00', + 'Network': '#90ee90', + 'Barrier': '#8b4513', + 'Finish': '#32cd32', + 'Channel': '#708090' +}; + +// Initialize on page load +document.addEventListener('DOMContentLoaded', function() { + state.canvas = document.getElementById('mazeCanvas'); + state.ctx = state.canvas.getContext('2d'); + + // Set canvas size + state.canvas.width = 700; + state.canvas.height = 600; + + // Event listeners + document.getElementById('sampleMazeBtn').addEventListener('click', createSampleMaze); + document.getElementById('randomMazeBtn').addEventListener('click', createRandomMaze); + document.getElementById('playBtn').addEventListener('click', playAnimation); + document.getElementById('pauseBtn').addEventListener('click', pauseAnimation); + document.getElementById('resetBtn').addEventListener('click', resetAnimation); + + // Start animation loop + gameLoop(); + + updateStatus('Ready to create maze'); +}); + +async function createSampleMaze() { + try { + updateStatus('Creating sample maze...'); + const response = await fetch('/api/create_maze', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ type: 'sample' }) + }); + const data = await response.json(); + state.maze = data.maze; + const height = data.maze.length; + const width = data.maze[0].length; + // Robot starts at bottom-left corner + state.robot = { x: 0, y: height - 1 }; + state.cellTransformations = {}; + state.iterator = null; + state.isRunning = false; + state.isPaused = false; + state.currentStep = 0; + state.animationState = 'idle'; + state.transformationQueue = []; + updateStatus('Sample maze created (12x10). Press Play to start animation.'); + updateUI(); + } catch (error) { + updateStatus('Error creating maze: ' + error.message); + } +} + +async function createRandomMaze() { + try { + const inputWidth = parseInt(document.getElementById('widthInput').value) || 15; + const inputHeight = parseInt(document.getElementById('heightInput').value) || 15; + + if (inputWidth < 5 || inputWidth > 30 || inputHeight < 5 || inputHeight > 30) { + updateStatus('Maze dimensions must be between 5 and 30'); + return; + } + + updateStatus('Creating random maze...'); + const response = await fetch('/api/create_maze', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ type: 'random', width: inputWidth, height: inputHeight }) + }); + const data = await response.json(); + state.maze = data.maze; + const mazeHeight = data.maze.length; + const mazeWidth = data.maze[0].length; + // Robot starts at bottom-left corner + state.robot = { x: 0, y: mazeHeight - 1 }; + state.cellTransformations = {}; + state.iterator = null; + state.isRunning = false; + state.isPaused = false; + state.currentStep = 0; + state.animationState = 'idle'; + state.transformationQueue = []; + updateStatus(`Random maze created (${mazeWidth}x${mazeHeight}). Press Play to start animation.`); + updateUI(); + } catch (error) { + updateStatus('Error creating maze: ' + error.message); + } +} + +async function playAnimation() { + if (!state.maze) { + updateStatus('Create a maze first'); + return; + } + + if (!state.iterator) { + try { + updateStatus('Getting traversal path...'); + const response = await fetch('/api/get_iterator', { method: 'GET' }); + const data = await response.json(); + state.iterator = data.iterator; + state.totalSteps = state.iterator.length; + state.currentStep = 0; + + if (state.iterator.length === 0) { + updateStatus('No cells to visit in this maze'); + return; + } + } catch (error) { + updateStatus('Error getting iterator: ' + error.message); + return; + } + } + + state.isRunning = true; + state.isPaused = false; + document.getElementById('playBtn').disabled = true; + document.getElementById('pauseBtn').disabled = false; + updateStatus('Animation running...'); +} + +function pauseAnimation() { + state.isPaused = !state.isPaused; + const btn = document.getElementById('pauseBtn'); + if (state.isPaused) { + btn.textContent = 'Resume'; + btn.classList.remove('btn-warning'); + btn.classList.add('btn-success'); + updateStatus('Animation paused'); + } else { + btn.textContent = 'Pause'; + btn.classList.remove('btn-success'); + btn.classList.add('btn-warning'); + updateStatus('Animation resumed'); + } +} + +function resetAnimation() { + state.isRunning = false; + state.isPaused = false; + state.animationState = 'idle'; + state.moveProgress = 0; + state.transformProgress = 0; + state.transformationQueue = []; + state.transformationTimer = 0; + state.currentStep = 0; + state.iterator = null; + + document.getElementById('playBtn').disabled = false; + document.getElementById('pauseBtn').disabled = true; + document.getElementById('pauseBtn').textContent = 'Pause'; + document.getElementById('pauseBtn').classList.remove('btn-success'); + document.getElementById('pauseBtn').classList.add('btn-warning'); + + updateStatus('Animation reset. Press Play to start.'); + updateUI(); +} + +function gameLoop() { + const now = Date.now(); + const dt = (now - state.lastFrameTime) / 1000; + state.lastFrameTime = now; + + if (state.isRunning && !state.isPaused && state.iterator) { + updateAnimation(dt); + } + + draw(); + requestAnimationFrame(gameLoop); +} + +function updateAnimation(dt) { + if (state.animationState === 'idle') { + // Move to next cell + if (state.currentStep < state.iterator.length) { + const nextCellIndex = state.iterator[state.currentStep]; + const maze = state.maze; + const width = maze[0].length; + state.currentCell = { + x: nextCellIndex % width, + y: Math.floor(nextCellIndex / width) + }; + state.animationState = 'moving'; + state.moveProgress = 0; + } else { + // Animation finished + state.isRunning = false; + document.getElementById('playBtn').disabled = false; + document.getElementById('pauseBtn').disabled = true; + updateStatus('Animation finished!'); + } + } + + if (state.animationState === 'moving') { + state.moveProgress += dt / state.MOVE_DURATION; + + if (state.moveProgress >= 1.0) { + state.moveProgress = 1.0; + state.robot = state.currentCell; + state.animationState = 'transforming'; + + // Queue transformations based on cell type + const cellType = Object.keys(CellType).find( + key => CellType[key] === state.maze[state.robot.y][state.robot.x] + ); + + state.transformationQueue = []; + + if (cellType === 'Shore') { + state.transformationQueue.push('Shore'); + state.transformationQueue.push('Water'); + } else if (cellType === 'Water') { + state.transformationQueue.push('Water'); + } + + state.transformProgress = 0; + state.transformationTimer = 0; + } + } + + if (state.animationState === 'transforming') { + if (state.transformationQueue.length > 0) { + state.transformationTimer += dt; + + if (state.transformationTimer >= state.TRANSFORM_DURATION) { + // Apply the transformation + const transformation = state.transformationQueue.shift(); + const cellKey = `${state.robot.x},${state.robot.y}`; + + if (!state.cellTransformations[cellKey]) { + state.cellTransformations[cellKey] = []; + } + state.cellTransformations[cellKey].push(transformation); + + // Update maze + if (transformation === 'Shore') { + state.maze[state.robot.y][state.robot.x] = CellType['Water']; + } else if (transformation === 'Water') { + state.maze[state.robot.y][state.robot.x] = CellType['Sample']; + } + + state.transformationTimer = 0; + } + } else { + // No more transformations, move to next cell + state.currentStep++; + state.animationState = 'idle'; + state.moveProgress = 0; + } + } +} + +function draw() { + state.ctx.fillStyle = '#f5f5f5'; + state.ctx.fillRect(0, 0, state.canvas.width, state.canvas.height); + + if (!state.maze) { + state.ctx.fillStyle = '#999'; + state.ctx.font = '16px Arial'; + state.ctx.textAlign = 'center'; + state.ctx.fillText('Create a maze to begin', state.canvas.width / 2, state.canvas.height / 2); + return; + } + + // Calculate cell size to fit maze + const mazeWidth = state.maze[0].length; + const mazeHeight = state.maze.length; + state.cellSize = Math.min( + (state.canvas.width - 20) / mazeWidth, + (state.canvas.height - 20) / mazeHeight + ); + + const startX = (state.canvas.width - mazeWidth * state.cellSize) / 2; + const startY = (state.canvas.height - mazeHeight * state.cellSize) / 2; + + // Draw maze + for (let y = 0; y < mazeHeight; y++) { + for (let x = 0; x < mazeWidth; x++) { + const cellType = state.maze[y][x]; + const typeName = Object.keys(CellType).find(key => CellType[key] === cellType); + const px = startX + x * state.cellSize; + const py = startY + y * state.cellSize; + + state.ctx.fillStyle = CellColors[typeName] || '#ccc'; + state.ctx.fillRect(px, py, state.cellSize, state.cellSize); + + state.ctx.strokeStyle = '#aaa'; + state.ctx.lineWidth = 1; + state.ctx.strokeRect(px, py, state.cellSize, state.cellSize); + } + } + + // Draw robot with animation + if (state.robot) { + const targetX = startX + state.robot.x * state.cellSize; + const targetY = startY + state.robot.y * state.cellSize; + + let drawX = targetX; + let drawY = targetY; + + // Interpolate position during movement + if (state.animationState === 'moving' && state.currentCell) { + const startCellX = startX + (state.robot.x || state.currentCell.x) * state.cellSize; + const startCellY = startY + (state.robot.y || state.currentCell.y) * state.cellSize; + const endCellX = startX + state.currentCell.x * state.cellSize; + const endCellY = startY + state.currentCell.y * state.cellSize; + + drawX = startCellX + (endCellX - startCellX) * state.moveProgress; + drawY = startCellY + (endCellY - startCellY) * state.moveProgress; + } + + // Draw robot + state.ctx.fillStyle = '#ff0000'; + state.ctx.beginPath(); + state.ctx.arc( + drawX + state.cellSize / 2, + drawY + state.cellSize / 2, + state.cellSize / 3, + 0, + Math.PI * 2 + ); + state.ctx.fill(); + + state.ctx.strokeStyle = '#cc0000'; + state.ctx.lineWidth = 2; + state.ctx.stroke(); + } +} + +function updateUI() { + const canvas = state.canvas; + canvas.width = 700; + canvas.height = 600; +} + +function updateStatus(message) { + document.getElementById('statusText').textContent = message; +} + +function updateMessage(message) { + document.getElementById('messageText').textContent = message; +} diff --git "a/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/static/style.css" "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/static/style.css" new file mode 100644 index 0000000..4098420 --- /dev/null +++ "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/static/style.css" @@ -0,0 +1,284 @@ +* { + 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; + padding: 20px; +} + +.container { + max-width: 1400px; + margin: 0 auto; + background: white; + border-radius: 10px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); + overflow: hidden; +} + +.header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 30px; + text-align: center; +} + +.header h1 { + font-size: 2.5em; + margin-bottom: 10px; +} + +.header p { + font-size: 1.1em; + opacity: 0.9; +} + +.main-content { + display: flex; + gap: 20px; + padding: 20px; + height: 600px; +} + +.left-panel { + width: 300px; + display: flex; + flex-direction: column; + gap: 20px; + overflow-y: auto; +} + +.right-panel { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + background: #f5f5f5; + border-radius: 8px; + padding: 20px; +} + +canvas { + max-width: 100%; + max-height: 100%; + border: 2px solid #ddd; + border-radius: 4px; +} + +/* Controls */ +.controls, .legend, .info-panel { + background: #f9f9f9; + border-radius: 8px; + padding: 15px; + border: 1px solid #e0e0e0; +} + +.controls h2, +.legend h3, +.info-panel h3 { + color: #333; + margin-bottom: 15px; + font-size: 1.1em; +} + +.setup-section, .animation-section { + margin-bottom: 15px; +} + +.setup-section h3, .animation-section h3 { + font-size: 0.95em; + color: #555; + margin-bottom: 10px; +} + +label { + display: block; + margin-bottom: 8px; + color: #666; +} + +input[type="number"] { + width: 60px; + padding: 5px; + margin-left: 5px; + border: 1px solid #ddd; + border-radius: 4px; +} + +/* Buttons */ +.btn { + padding: 10px 15px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.9em; + font-weight: 500; + transition: all 0.3s ease; + margin: 5px 5px 5px 0; + width: 100%; +} + +.btn-primary { + background: #667eea; + color: white; +} + +.btn-primary:hover { + background: #5568d3; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); +} + +.btn-success { + background: #48bb78; + color: white; +} + +.btn-success:hover:not(:disabled) { + background: #38a169; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(72, 187, 120, 0.4); +} + +.btn-warning { + background: #ed8936; + color: white; +} + +.btn-warning:hover:not(:disabled) { + background: #dd6b20; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(237, 137, 54, 0.4); +} + +.btn-danger { + background: #f56565; + color: white; +} + +.btn-danger:hover:not(:disabled) { + background: #e53e3e; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(245, 101, 101, 0.4); +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Progress Bar */ +.progress-bar { + width: 100%; + height: 8px; + background: #e0e0e0; + border-radius: 4px; + overflow: hidden; + margin: 10px 0; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, #667eea, #764ba2); + width: 0%; + transition: width 0.3s ease; +} + +#stepInfo { + font-size: 0.85em; + color: #666; + text-align: center; + margin-top: 5px; +} + +/* Legend */ +.legend-item { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 8px; + font-size: 0.9em; +} + +.color-box { + width: 20px; + height: 20px; + border-radius: 3px; + border: 1px solid #999; +} + +.color-box.water { + background: #4682b4; +} + +.color-box.shore { + background: #d2b48c; +} + +.color-box.sample { + background: #ff8c00; +} + +.color-box.barrier { + background: #8b4513; +} + +.color-box.finish { + background: #32cd32; +} + +.color-box.robot { + background: #ff0000; +} + +/* Info Panel */ +.info-panel { + flex: 1; + display: flex; + flex-direction: column; +} + +#statusText { + color: #667eea; + font-weight: 500; + margin-bottom: 10px; + font-size: 0.9em; +} + +#messageText { + color: #48bb78; + font-size: 0.85em; + min-height: 40px; + word-wrap: break-word; +} + +/* Responsive */ +@media (max-width: 1024px) { + .main-content { + flex-direction: column; + height: auto; + } + + .left-panel { + width: 100%; + max-height: 200px; + } + + .right-panel { + height: 500px; + } +} + +@media (max-width: 768px) { + .header h1 { + font-size: 1.8em; + } + + .right-panel { + height: 300px; + } +} diff --git "a/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/templates/index.html" "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/templates/index.html" new file mode 100644 index 0000000..74a8cd2 --- /dev/null +++ "b/5030102_30202/\320\222\320\276\321\200\320\276\320\275\321\206\320\276\320\262_\320\220\320\220/web/templates/index.html" @@ -0,0 +1,88 @@ + + +
+ + +Watch the robot explore and transform the hydro network
+