From e0a1e69fa8973ab9bc845576b75c10fb5540eef4 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Mon, 29 Sep 2025 10:44:14 +0800 Subject: [PATCH] added other examples --- .github/workflows/ci.yml | 71 +-- .github/workflows/publish.yml | 15 +- elevator_saga/client/api_client.py | 18 +- elevator_saga/client/proxy_models.py | 124 ++-- elevator_saga/core/models.py | 27 +- elevator_saga/server/simulator.py | 409 +++++++------ elevator_saga/traffic/down_peak.json | 458 +++++++++++++++ elevator_saga/traffic/fire_evacuation.json | 182 ++++++ elevator_saga/traffic/generators.py | 2 +- elevator_saga/traffic/high_density.json | 410 +++++++++++++ elevator_saga/traffic/inter_floor.json | 362 +++++++++--- elevator_saga/traffic/lunch_rush.json | 164 ++++++ elevator_saga/traffic/medical.json | 494 ++++++++++++++++ elevator_saga/traffic/meeting_event.json | 314 ++++++++++ elevator_saga/traffic/mixed_scenario.json | 614 ++++++++++++++++++++ elevator_saga/traffic/progressive_test.json | 590 +++++++++++++++++++ elevator_saga/traffic/random.json | 482 +++++++++++++++ elevator_saga/traffic/up_peak.json | 494 ++++++++++++++++ pyproject.toml | 154 +++++ run_all_tests.py | 157 +++++ setup.py | 12 +- test_example.py | 33 +- 22 files changed, 5126 insertions(+), 460 deletions(-) create mode 100644 elevator_saga/traffic/down_peak.json create mode 100644 elevator_saga/traffic/fire_evacuation.json create mode 100644 elevator_saga/traffic/high_density.json create mode 100644 elevator_saga/traffic/lunch_rush.json create mode 100644 elevator_saga/traffic/medical.json create mode 100644 elevator_saga/traffic/meeting_event.json create mode 100644 elevator_saga/traffic/mixed_scenario.json create mode 100644 elevator_saga/traffic/progressive_test.json create mode 100644 elevator_saga/traffic/random.json create mode 100644 elevator_saga/traffic/up_peak.json create mode 100644 pyproject.toml create mode 100644 run_all_tests.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5ca0fb..e9edfff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + os: [ubuntu-latest] + python-version: ['3.12'] steps: - uses: actions/checkout@v4 @@ -31,14 +31,14 @@ jobs: uses: actions/cache@v3 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml', '**/setup.py') }} restore-keys: | ${{ runner.os }}-pip- - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . + pip install -e .[dev] - name: Check dependencies run: | @@ -46,19 +46,19 @@ jobs: - name: Run linting run: | - black --check msgcenterpy tests - isort --check-only msgcenterpy tests + black --check elevator_saga tests + isort --check-only elevator_saga tests - name: Run type checking run: | - mypy msgcenterpy + mypy elevator_saga - name: Run tests with coverage run: | - python -m pytest --cov=msgcenterpy --cov-report=xml --cov-report=term-missing + python -m pytest --cov=elevator_saga --cov-report=xml --cov-report=term-missing - name: Upload coverage to Codecov - if: matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest' + if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v3 with: file: ./coverage.xml @@ -66,43 +66,26 @@ jobs: name: codecov-umbrella fail_ci_if_error: false - test-with-ros2: - name: Test with ROS2 (Ubuntu) + test-examples: + name: Test examples runs-on: ubuntu-latest - container: - image: ros:humble - + steps: - uses: actions/checkout@v4 - name: Set up Python - run: | - apt-get update - apt-get install -y python3-pip python3-dev + uses: actions/setup-python@v4 + with: + python-version: '3.12' - - name: Install ROS2 dependencies + - name: Install dependencies run: | - apt-get update - apt-get install -y \ - python3-rosidl-runtime-py \ - python3-rclpy \ - ros-humble-std-msgs \ - ros-humble-geometry-msgs - - - name: Install package - run: | - python3 -m pip install --upgrade pip - pip3 install -e .[dev,ros2] + python -m pip install --upgrade pip + pip install -e .[dev] - - name: Run ROS2 tests + - name: Run example tests run: | - . /opt/ros/humble/setup.sh - python3 run_all_tests.py --type ros2 - - - name: Run conversion tests - run: | - . /opt/ros/humble/setup.sh - python3 run_all_tests.py --type conversion + python run_all_tests.py --type examples build: name: Build and check package @@ -114,7 +97,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.12' - name: Install build dependencies run: | @@ -146,7 +129,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.12' - name: Install security tools run: | @@ -154,7 +137,7 @@ jobs: pip install bandit safety - name: Run bandit security scan - run: bandit -r msgcenterpy/ -f json -o bandit-report.json + run: bandit -r elevator_saga/ -f json -o bandit-report.json continue-on-error: true - name: Run safety security scan @@ -180,12 +163,12 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.12' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e .[docs] + pip install -e .[dev][docs] # 为将来的文档构建预留 - name: Check documentation @@ -204,12 +187,12 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.12' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . + pip install -e .[dev] pip install pytest-benchmark # 为将来的性能测试预留 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ecfae7b..ea5c34c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,7 +22,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.12' # - name: Install dependencies # run: | @@ -50,7 +50,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.12' # - name: Install build dependencies # run: | @@ -60,7 +60,7 @@ jobs: # - name: Verify version consistency # run: | # # 检查版本号一致性 - # VERSION=$(python -c "import msgcenterpy; print(msgcenterpy.__version__)" 2>/dev/null || echo "unknown") + # VERSION=$(python -c "import elevator_saga; print(elevator_saga.__version__)" 2>/dev/null || echo "unknown") # TAG_VERSION="${GITHUB_REF#refs/tags/v}" # if [ "$GITHUB_EVENT_NAME" = "release" ]; then # if [ "$VERSION" != "$TAG_VERSION" ]; then @@ -72,6 +72,11 @@ jobs: # - name: Check manifest # run: check-manifest + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install build twine check-manifest + - name: Build package run: | python -m build @@ -94,7 +99,7 @@ jobs: if: github.event.inputs.test_pypi == 'true' || (github.event_name == 'release' && github.event.release.prerelease) environment: name: test-pypi - url: https://test.pypi.org/p/elevator-py + url: https://test.pypi.org/p/elevator-saga steps: - name: Download build artifacts @@ -117,7 +122,7 @@ jobs: if: github.event_name == 'release' && !github.event.release.prerelease && github.event.inputs.test_pypi != 'true' environment: name: pypi - url: https://pypi.org/p/elevator-py + url: https://pypi.org/p/elevator-saga steps: - name: Download build artifacts diff --git a/elevator_saga/client/api_client.py b/elevator_saga/client/api_client.py index ac53388..31d0a17 100644 --- a/elevator_saga/client/api_client.py +++ b/elevator_saga/client/api_client.py @@ -14,7 +14,6 @@ from elevator_saga.core.models import ( GoToFloorCommand, PassengerInfo, PerformanceMetrics, - SetIndicatorsCommand, SimulationEvent, SimulationState, StepResponse, @@ -132,7 +131,7 @@ class ElevatorAPIClient: else: raise RuntimeError(f"Step failed: {response_data.get('error')}") - def send_elevator_command(self, command: Union[GoToFloorCommand, SetIndicatorsCommand]) -> bool: + def send_elevator_command(self, command: Union[GoToFloorCommand]) -> bool: """发送电梯命令""" endpoint = self._get_elevator_endpoint(command) debug_log(f"Sending elevator command: {command.command_type} to elevator {command.elevator_id} To:F{command.floor}") @@ -155,25 +154,12 @@ class ElevatorAPIClient: debug_log(f"Go to floor failed: {e}") return False - def set_indicators(self, elevator_id: int, up: Optional[bool] = None, down: Optional[bool] = None) -> bool: - """设置电梯指示灯""" - command = SetIndicatorsCommand(elevator_id=elevator_id, up=up, down=down) - - try: - response = self.send_elevator_command(command) - return response - except Exception as e: - debug_log(f"Set indicators failed: {e}") - return False - - def _get_elevator_endpoint(self, command: Union[GoToFloorCommand, SetIndicatorsCommand]) -> str: + def _get_elevator_endpoint(self, command: Union[GoToFloorCommand]) -> str: """获取电梯命令端点""" base = f"/api/elevators/{command.elevator_id}" if isinstance(command, GoToFloorCommand): return f"{base}/go_to_floor" - else: # SetIndicatorsCommand - return f"{base}/set_indicators" def _send_get_request(self, endpoint: str) -> Dict[str, Any]: """发送GET请求""" diff --git a/elevator_saga/client/proxy_models.py b/elevator_saga/client/proxy_models.py index 2d054c6..edc8a9a 100644 --- a/elevator_saga/client/proxy_models.py +++ b/elevator_saga/client/proxy_models.py @@ -10,48 +10,36 @@ class ProxyFloor(FloorState): 直接使用 FloorState 数据模型实例,提供完整的类型安全访问 """ - init_ok = False + _init_ok = False def __init__(self, floor_id: int, api_client: ElevatorAPIClient): self._floor_id = floor_id self._api_client = api_client self._cached_instance = None - self.init_ok = True + self._init_ok = True def _get_floor_state(self) -> FloorState: """获取 FloorState 实例""" - # 获取当前状态 state = self._api_client.get_state() floor_data = next((f for f in state.floors if f.floor == self._floor_id), None) + return floor_data - if floor_data is None: - raise AttributeError(f"Floor {self._floor_id} not found") - - # 如果是字典,转换为 FloorState 实例 - if isinstance(floor_data, dict): - return FloorState.from_dict(floor_data) + def __getattribute__(self, name: str) -> Any: + if not name.startswith("_") and self._init_ok and name not in self.__class__.__dict__: + try: + self_attr = object.__getattribute__(self, name) + if callable(self_attr): + return object.__getattribute__(self, name) + except AttributeError: + pass + floor_state = self._get_floor_state() + return floor_state.__getattribute__(name) else: - # 如果已经是 FloorState 实例,直接返回 - return floor_data - - def __getattr__(self, name: str) -> Any: - """动态获取楼层属性""" - floor_state = self._get_floor_state() - try: - if hasattr(floor_state, name): - attr = getattr(floor_state, name) - # 如果是 property 或方法,调用并返回结果 - if callable(attr): - return attr() - else: - return attr - except AttributeError: - - raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") + return object.__getattribute__(self, name) def __setattr__(self, name: str, value: Any) -> None: """禁止修改属性,保持只读特性""" - if not self.init_ok: + if not self._init_ok: object.__setattr__(self, name, value) else: raise AttributeError(f"Cannot modify read-only attribute '{name}'") @@ -66,54 +54,40 @@ class ProxyElevator(ElevatorState): 直接使用 ElevatorState 数据模型实例,提供完整的类型安全访问和操作方法 """ - init_ok = False + _init_ok = False def __init__(self, elevator_id: int, api_client: ElevatorAPIClient): self._elevator_id = elevator_id self._api_client = api_client - self.init_ok = True + self._init_ok = True def _get_elevator_state(self) -> ElevatorState: """获取 ElevatorState 实例""" # 获取当前状态 state = self._api_client.get_state() elevator_data = next((e for e in state.elevators if e.id == self._elevator_id), None) + return elevator_data - if elevator_data is None: - raise AttributeError(f"Elevator {self._elevator_id} not found") - - # 如果是字典,转换为 ElevatorState 实例 - if isinstance(elevator_data, dict): - return ElevatorState.from_dict(elevator_data) - else: - # 如果已经是 ElevatorState 实例,直接返回 - return elevator_data - - def __getattr__(self, name: str) -> Any: - """动态获取电梯属性""" - try: + def __getattribute__(self, name: str) -> Any: + if not name.startswith("_") and self._init_ok and name not in self.__class__.__dict__: + try: + self_attr = object.__getattribute__(self, name) + if callable(self_attr): + return object.__getattribute__(self, name) + except AttributeError: + pass elevator_state = self._get_elevator_state() - # 直接从 ElevatorState 实例获取属性 - return getattr(elevator_state, name) - - except AttributeError: - raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") + return elevator_state.__getattribute__(name) + else: + return object.__getattribute__(self, name) def go_to_floor(self, floor: int, immediate: bool = False) -> bool: """前往指定楼层""" return self._api_client.go_to_floor(self._elevator_id, floor, immediate) - def set_up_indicator(self, value: bool) -> None: - """设置上行指示器""" - self._api_client.set_indicators(self._elevator_id, up=value) - - def set_down_indicator(self, value: bool) -> None: - """设置下行指示器""" - self._api_client.set_indicators(self._elevator_id, down=value) - def __setattr__(self, name: str, value: Any) -> None: """禁止修改属性,保持只读特性""" - if not self.init_ok: + if not self._init_ok: object.__setattr__(self, name, value) else: raise AttributeError(f"Cannot modify read-only attribute '{name}'") @@ -128,43 +102,35 @@ class ProxyPassenger(PassengerInfo): 直接使用 PassengerInfo 数据模型实例,提供完整的类型安全访问 """ - init_ok = False + _init_ok = False def __init__(self, passenger_id: int, api_client: ElevatorAPIClient): self._passenger_id = passenger_id self._api_client = api_client - self.init_ok = True + self._init_ok = True def _get_passenger_info(self) -> PassengerInfo: """获取 PassengerInfo 实例""" - # 获取当前状态 state = self._api_client.get_state() - - # 在乘客字典中查找 passenger_data = state.passengers.get(self._passenger_id) - if passenger_data is None: - raise AttributeError(f"Passenger {self._passenger_id} not found") + return passenger_data - # 如果是字典,转换为 PassengerInfo 实例 - if isinstance(passenger_data, dict): - return PassengerInfo.from_dict(passenger_data) + def __getattribute__(self, name: str) -> Any: + if not name.startswith("_") and self._init_ok and name not in self.__class__.__dict__: + try: + self_attr = object.__getattribute__(self, name) + if callable(self_attr): + return object.__getattribute__(self, name) + except AttributeError: + pass + psg_info = self._get_passenger_info() + return psg_info.__getattribute__(name) else: - # 如果已经是 PassengerInfo 实例,直接返回 - return passenger_data - - def __getattr__(self, name: str) -> Any: - """动态获取乘客属性""" - try: - passenger_info = self._get_passenger_info() - # 直接从 PassengerInfo 实例获取属性 - return getattr(passenger_info, name) - - except AttributeError: - raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") + return object.__getattribute__(self, name) def __setattr__(self, name: str, value: Any) -> None: """禁止修改属性,保持只读特性""" - if not self.init_ok: + if not self._init_ok: object.__setattr__(self, name, value) else: raise AttributeError(f"Cannot modify read-only attribute '{name}'") diff --git a/elevator_saga/core/models.py b/elevator_saga/core/models.py index f652adf..7b29103 100644 --- a/elevator_saga/core/models.py +++ b/elevator_saga/core/models.py @@ -78,7 +78,13 @@ class SerializableModel: valid_params = set(sig.parameters.keys()) - {"self"} filtered_data = {k: v for k, v in data.items() if k in valid_params} - return cls(**filtered_data) + instance = cls(**filtered_data) + for k, v in cls.__dict__.items(): + if issubclass(v.__class__, Enum): # 要求不能为None + value = getattr(instance, k) + setattr(instance, k, v.__class__(value)) + return instance + @classmethod def from_json(cls: Type[T], json_str: str) -> T: @@ -200,6 +206,7 @@ class ElevatorState(SerializableModel): max_capacity: int = 10 speed_pre_tick: float = 0.5 run_status: ElevatorStatus = ElevatorStatus.STOPPED + last_tick_direction: Direction = Direction.STOPPED indicators: ElevatorIndicators = field(default_factory=ElevatorIndicators) passenger_destinations: Dict[int, int] = field(default_factory=dict) # type: ignore[reportUnknownVariableType] 乘客ID -> 目的地楼层映射 energy_consumed: float = 0.0 @@ -435,7 +442,7 @@ class ElevatorCommand(SerializableModel): """电梯命令""" elevator_id: int - command_type: str # "go_to_floor", "stop", "set_indicators" + command_type: str # "go_to_floor", "stop" parameters: Dict[str, Any] = field(default_factory=dict) # type: ignore[reportUnknownVariableType] request_id: str = field(default_factory=lambda: str(uuid.uuid4())) timestamp: str = field(default_factory=lambda: datetime.now().isoformat()) @@ -465,22 +472,6 @@ class GoToFloorCommand(SerializableModel): return {"floor": self.floor, "immediate": self.immediate} -@dataclass -class SetIndicatorsCommand(SerializableModel): - """设置指示灯命令""" - - elevator_id: int - up: Optional[bool] = None - down: Optional[bool] = None - command_type: str = field(default="set_indicators", init=False) - request_id: str = field(default_factory=lambda: str(uuid.uuid4())) - timestamp: str = field(default_factory=lambda: datetime.now().isoformat()) - - @property - def parameters(self) -> Dict[str, Any]: - return {"up": self.up, "down": self.down} - - # ==================== 流量和配置数据模型 ==================== diff --git a/elevator_saga/server/simulator.py b/elevator_saga/server/simulator.py index d3d8565..56a2911 100644 --- a/elevator_saga/server/simulator.py +++ b/elevator_saga/server/simulator.py @@ -273,8 +273,13 @@ class ElevatorSimulation: return new_events def _process_tick(self) -> List[SimulationEvent]: - """Process one simulation tick""" + """ + Process one simulation tick + 每个tick先发生事件,再发生动作 + """ events_start = len(self.state.events) + self._update_elevator_status() + # 1. Add new passengers from traffic queue self._process_arrivals() @@ -287,7 +292,65 @@ class ElevatorSimulation: # Return events generated this tick return self.state.events[events_start:] - def _process_arrivals(self) -> None: + def _process_passenger_in(self) -> None: + for elevator in self.elevators: + current_floor = elevator.current_floor + # 处于Stopped状态,方向也已经清空,说明没有调度。 + floor = self.floors[current_floor] + passengers_to_board: List[int] = [] + available_capacity = elevator.max_capacity - len(elevator.passengers) + # Board passengers going up (if up indicator is on or no direction set) + if elevator.target_floor_direction == Direction.UP: + passengers_to_board.extend(floor.up_queue[:available_capacity]) + floor.up_queue = floor.up_queue[available_capacity:] + + # Board passengers going down (if down indicator is on or no direction set) + if elevator.target_floor_direction == Direction.DOWN: + passengers_to_board.extend(floor.down_queue[:available_capacity]) + floor.down_queue = floor.down_queue[available_capacity:] + + # Process boarding + for passenger_id in passengers_to_board: + passenger = self.passengers[passenger_id] + passenger.pickup_tick = self.tick + passenger.elevator_id = elevator.id + elevator.passengers.append(passenger_id) + self._emit_event( + EventType.PASSENGER_BOARD, + {"elevator": elevator.id, "floor": current_floor, "passenger": passenger_id}, + ) + + def _update_elevator_status(self) -> None: + """更新电梯运行状态""" + for elevator in self.elevators: + current_floor = elevator.position.current_floor + target_floor = elevator.target_floor + old_status = elevator.run_status.value + # 没有移动方向,说明电梯已经到达目标楼层 + if elevator.target_floor_direction == Direction.STOPPED: + if elevator.next_target_floor is not None: + self._set_elevator_target_floor(elevator, elevator.next_target_floor) + + self._process_passenger_in() + elevator.next_target_floor = None + else: + continue + # 有移动方向,但是需要启动了 + if elevator.run_status == ElevatorStatus.STOPPED: + # 从停止状态启动 - 注意:START_UP表示启动加速状态,不表示方向 + # 实际移动方向由target_floor_direction决定 + elevator.run_status = ElevatorStatus.START_UP + # 从启动状态切换到匀速 + elif elevator.run_status == ElevatorStatus.START_UP: + # 从启动状态切换到匀速 + elevator.run_status = ElevatorStatus.CONSTANT_SPEED + server_debug_log( + f"电梯{elevator.id} 状态:{old_status}->{elevator.run_status.value} 方向:{elevator.target_floor_direction.value} " + f"位置:{elevator.position.current_floor_float:.1f} 目标:{target_floor}" + ) + # START_DOWN状态会在到达目标时在_move_elevators中切换为STOPPED + + def _process_arrivals(self) -> None: # OK """Process new passenger arrivals""" while self.traffic_queue and self.traffic_queue[0].tick <= self.tick: traffic_entry = self.traffic_queue.pop(0) @@ -309,6 +372,130 @@ class ElevatorSimulation: self.floors[passenger.origin].down_queue.append(passenger.id) self._emit_event(EventType.DOWN_BUTTON_PRESSED, {"floor": passenger.origin, "passenger": passenger.id}) + def _move_elevators(self) -> None: + """ + Move all elevators towards their destinations with acceleration/deceleration + 上一步已经处理了当前电梯的状态,这里只做移动 + """ + for elevator in self.elevators: + target_floor = elevator.target_floor + new_floor = old_floor = elevator.position.current_floor + # 获取移动速度 + movement_speed = 0 + if elevator.run_status == ElevatorStatus.START_UP: + movement_speed = 1 + elif elevator.run_status == ElevatorStatus.START_DOWN: + movement_speed = 1 + elif elevator.run_status == ElevatorStatus.CONSTANT_SPEED: + movement_speed = 2 + if movement_speed == 0: + continue + + # 根据状态和方向调整移动距离 + elevator.last_tick_direction = elevator.target_floor_direction + if elevator.target_floor_direction == Direction.UP: + new_floor = elevator.position.floor_up_position_add(movement_speed) + elif elevator.target_floor_direction == Direction.DOWN: + new_floor = elevator.position.floor_up_position_add(-movement_speed) + else: + # 之前的状态已经是到站了,清空上一次到站的方向 + pass + + # 移动后检测是否即将到站,从匀速状态切换到减速 + if elevator.run_status == ElevatorStatus.CONSTANT_SPEED: + # 检查是否需要开始减速,这里加速减速设置路程为1,匀速路程为2,这样能够保证不会匀速恰好到达,必须加减速 + # 如果速度超出,则预期的逻辑是,恰好到达/超出0等,会强制触发start_down,多走一次才能stop,目前没有实现这部分逻辑 + if self._should_start_deceleration(elevator): + elevator.run_status = ElevatorStatus.START_DOWN + # 发送电梯即将经过某层楼事件 + if self._near_next_stop(elevator): + self._emit_event( + EventType.ELEVATOR_APPROACHING, + { + "elevator": elevator.id, + "floor": elevator.target_floor, + "direction": elevator.target_floor_direction.value, + }, + ) + + # 处理楼层变化事件 + if old_floor != new_floor: + if new_floor != target_floor: + self._emit_event( + EventType.PASSING_FLOOR, + { + "elevator": elevator.id, + "floor": new_floor, + "direction": elevator.target_floor_direction.value, + }, + ) + + # 检查是否到达目标楼层 + if target_floor == new_floor and elevator.position.floor_up_position == 0: + elevator.run_status = ElevatorStatus.STOPPED + # 刚进入Stopped状态,可以通过last_direction识别 + self._emit_event(EventType.STOPPED_AT_FLOOR, {"elevator": elevator.id, "floor": new_floor, "reason": "move_reached"}) + # elevator.energy_consumed += abs(direction * elevator.speed_pre_tick) * 0.5 + + def _process_elevator_stops(self) -> None: + """ + 处理Stopped电梯,上下客,新target处理等。 + """ + for elevator in self.elevators: + current_floor = elevator.current_floor + # 处于Stopped状态,方向也已经清空,说明没有调度。 + if elevator.last_tick_direction == Direction.STOPPED: + self._emit_event(EventType.IDLE, {"elevator": elevator.id, "floor": current_floor}) + continue + # 其他处于STOPPED状态,刚进入stop,到站要进行上下客 + if not elevator.run_status == ElevatorStatus.STOPPED: + continue + + # Let passengers alight + passengers_to_remove: List[int] = [] + for passenger_id in elevator.passengers: + passenger = self.passengers[passenger_id] + if passenger.destination == current_floor: + passenger.dropoff_tick = self.tick + passengers_to_remove.append(passenger_id) + + # Remove passengers who alighted + for passenger_id in passengers_to_remove: + elevator.passengers.remove(passenger_id) + self._emit_event( + EventType.PASSENGER_ALIGHT, + {"elevator": elevator.id, "floor": current_floor, "passenger": passenger_id}, + ) + # Board waiting passengers (if indicators allow) + if elevator.next_target_floor is not None: + self._set_elevator_target_floor(elevator, elevator.next_target_floor) + elevator.next_target_floor = None + + def _set_elevator_target_floor(self, elevator: ElevatorState, floor: int): + """ + 同一个tick内提示 + [SERVER-DEBUG] 电梯 E0 下一目的地设定为 F1 + [SERVER-DEBUG] 电梯 E0 被设定为前往 F1 + 说明电梯处于stop状态,这个tick直接采用下一个目的地运行了 + """ + original_target_floor = elevator.target_floor + elevator.position.target_floor = floor + server_debug_log(f"电梯 E{elevator.id} 被设定为前往 F{floor}") + new_target_floor_should_accel = self._should_start_deceleration(elevator) + if not new_target_floor_should_accel: + if elevator.run_status == ElevatorStatus.START_DOWN: # 不应该加速但是加了 + elevator.run_status = ElevatorStatus.CONSTANT_SPEED + server_debug_log(f"电梯 E{elevator.id} 被设定为匀速") + elif new_target_floor_should_accel: + if elevator.run_status == ElevatorStatus.CONSTANT_SPEED: # 应该减速了,但是之前是匀速 + elevator.run_status = ElevatorStatus.START_DOWN + server_debug_log(f"电梯 E{elevator.id} 被设定为减速") + if elevator.current_floor != floor or elevator.position.floor_up_position != 0: + old_status = elevator.run_status.value + server_debug_log( + f"电梯{elevator.id} 状态:{old_status}->{elevator.run_status.value}" + ) + def _calculate_distance_to_target(self, elevator: ElevatorState) -> float: """计算到目标楼层的距离(以floor_up_position为单位)""" current_pos = elevator.position.current_floor * 10 + elevator.position.floor_up_position @@ -336,214 +523,18 @@ class ElevatorSimulation: distance = self._calculate_distance_to_near_stop(elevator) return distance == 1 - def _get_movement_speed(self, elevator: ElevatorState) -> int: - """根据电梯状态获取移动速度""" - if elevator.run_status == ElevatorStatus.START_UP: - return 1 - elif elevator.run_status == ElevatorStatus.START_DOWN: - return 1 - elif elevator.run_status == ElevatorStatus.CONSTANT_SPEED: - return 2 - else: # STOPPED - return 0 - - def _update_elevator_status(self, elevator: ElevatorState) -> None: - """更新电梯运行状态""" - current_floor = elevator.position.current_floor - target_floor = elevator.target_floor - - if current_floor == target_floor and elevator.position.floor_up_position == 0: - # 已到达目标楼层 - elevator.run_status = ElevatorStatus.STOPPED - return - - if elevator.run_status == ElevatorStatus.STOPPED: - # 从停止状态启动 - 注意:START_UP表示启动加速状态,不表示方向 - # 实际移动方向由target_floor_direction决定 - elevator.run_status = ElevatorStatus.START_UP - elif elevator.run_status == ElevatorStatus.START_UP: - # 从启动状态切换到匀速 - elevator.run_status = ElevatorStatus.CONSTANT_SPEED - elif elevator.run_status == ElevatorStatus.CONSTANT_SPEED: - # 检查是否需要开始减速,这里加速减速设置路程为1,匀速路程为2,这样能够保证不会匀速恰好到达,必须加减速 - # 如果速度超出,则预期的逻辑是,恰好到达/超出0等,会强制触发start_down,多走一次才能stop,目前没有实现这部分逻辑 - if self._should_start_deceleration(elevator): - elevator.run_status = ElevatorStatus.START_DOWN - # 发送电梯即将经过某层楼事件 - if self._near_next_stop(elevator): - self._emit_event( - EventType.ELEVATOR_APPROACHING, - { - "elevator": elevator.id, - "floor": elevator.target_floor, - "direction": elevator.target_floor_direction.value, - }, - ) - # START_DOWN状态会在到达目标时自动切换为STOPPED - - def _move_elevators(self) -> None: - """Move all elevators towards their destinations with acceleration/deceleration""" - for elevator in self.elevators: - target_floor = elevator.target_floor - current_floor = elevator.position.current_floor - - # 如果已在恰好目标楼层,标记为STOPPED,之后交给_process_elevator_stops处理 - if target_floor == current_floor: - if elevator.next_target_floor is None: - continue - if elevator.position.floor_up_position == 0: - server_debug_log( - f"电梯{elevator.id}已在目标楼层,当前{elevator.position.current_floor_float} / 目标{target_floor}" - ) - elevator.run_status = ElevatorStatus.STOPPED - continue - - # 获取移动速度 - movement_speed = self._get_movement_speed(elevator) - - if movement_speed == 0: - continue - - # Move towards target - old_floor = current_floor - - # 根据状态和方向调整移动速度 - if elevator.target_floor_direction == Direction.UP: - # 向上移动 - new_floor = elevator.position.floor_up_position_add(movement_speed) - else: - # 向下移动 - new_floor = elevator.position.floor_up_position_add(-movement_speed) - - # 更新电梯状态 - old_status = elevator.run_status.value - self._update_elevator_status(elevator) - server_debug_log( - f"电梯{elevator.id} 状态:{old_status}->{elevator.run_status.value} 方向:{elevator.target_floor_direction.value} 速度:{movement_speed} " - f"位置:{elevator.position.current_floor_float:.1f} 目标:{target_floor}" - ) - - # 处理楼层变化事件 - if old_floor != new_floor: - if new_floor != target_floor: - self._emit_event( - EventType.PASSING_FLOOR, - { - "elevator": elevator.id, - "floor": new_floor, - "direction": elevator.target_floor_direction.value, - }, - ) - - # 检查是否到达目标楼层 - if target_floor == new_floor and elevator.position.floor_up_position == 0: - elevator.run_status = ElevatorStatus.STOPPED - self._emit_event(EventType.STOPPED_AT_FLOOR, {"elevator": elevator.id, "floor": new_floor}) - elevator.indicators.set_direction(elevator.target_floor_direction) # 抵达目标楼层,说明该取消floor了 - # elevator.energy_consumed += abs(direction * elevator.speed_pre_tick) * 0.5 - - def _process_elevator_stops(self) -> None: - """Handle passenger boarding and alighting at elevator stops""" - for elevator in self.elevators: - if not elevator.run_status == ElevatorStatus.STOPPED: - continue - current_floor = elevator.current_floor - - # Let passengers alight - passengers_to_remove: List[int] = [] - for passenger_id in elevator.passengers: - passenger = self.passengers[passenger_id] - if passenger.destination == current_floor: - passenger.dropoff_tick = self.tick - passengers_to_remove.append(passenger_id) - - # Remove passengers who alighted - for passenger_id in passengers_to_remove: - elevator.passengers.remove(passenger_id) - self._emit_event( - EventType.PASSENGER_ALIGHT, - {"elevator": elevator.id, "floor": current_floor, "passenger": passenger_id}, - ) - # Board waiting passengers (if indicators allow) - floor = self.floors[current_floor] - passengers_to_board: List[int] = [] - if not elevator.indicators.up and not elevator.indicators.down: - if elevator.next_target_floor is not None: - self._set_elevator_target_floor(elevator, elevator.next_target_floor) - elevator.next_target_floor = None - elevator.indicators.set_direction(elevator.target_floor_direction) - - # Board passengers going up (if up indicator is on or no direction set) - if elevator.indicators.up: - available_capacity = elevator.max_capacity - len(elevator.passengers) - passengers_to_board.extend(floor.up_queue[:available_capacity]) - floor.up_queue = floor.up_queue[available_capacity:] - - # Board passengers going down (if down indicator is on or no direction set) - if elevator.indicators.down: - # 先临时计算长度 - remaining_capacity = elevator.max_capacity - len(elevator.passengers) - len(passengers_to_board) - if remaining_capacity > 0: - down_passengers = floor.down_queue[:remaining_capacity] - passengers_to_board.extend(down_passengers) - floor.down_queue = floor.down_queue[remaining_capacity:] - - # 没有上下指示的时候,触发等待,会消耗一个tick - if not elevator.indicators.up and not elevator.indicators.down: - self._emit_event(EventType.IDLE, {"elevator": elevator.id, "floor": current_floor}) - continue - # Process boarding - for passenger_id in passengers_to_board: - passenger = self.passengers[passenger_id] - passenger.pickup_tick = self.tick - passenger.elevator_id = elevator.id - elevator.passengers.append(passenger_id) - self._emit_event( - EventType.PASSENGER_BOARD, - {"elevator": elevator.id, "floor": current_floor, "passenger": passenger_id}, - ) - - def _set_elevator_target_floor(self, elevator: ElevatorState, floor: int): - original_target_floor = elevator.target_floor - elevator.position.target_floor = floor - server_debug_log(f"电梯 E{elevator.id} 被设定为立刻前往 F{floor}") - new_target_floor_should_accel = self._should_start_deceleration(elevator) - if not new_target_floor_should_accel: - if elevator.run_status == ElevatorStatus.START_DOWN: # 不应该加速但是加了 - elevator.run_status = ElevatorStatus.CONSTANT_SPEED - server_debug_log(f"电梯 E{elevator.id} 被设定为匀速") - elif new_target_floor_should_accel: - if elevator.run_status == ElevatorStatus.CONSTANT_SPEED: # 应该减速了,但是之前是匀速 - elevator.run_status = ElevatorStatus.START_DOWN - server_debug_log(f"电梯 E{elevator.id} 被设定为减速") - if elevator.current_floor != floor or elevator.position.floor_up_position != 0: - old_status = elevator.run_status.value - self._update_elevator_status(elevator) - server_debug_log( - f"电梯{elevator.id} 状态:{old_status}->{elevator.run_status.value} 方向:{elevator.target_floor_direction.value} " - f"位置:{elevator.position.current_floor_float:.1f} 目标:{floor}" - ) - def elevator_go_to_floor(self, elevator_id: int, floor: int, immediate: bool = False) -> None: - """Command elevator to go to specified floor""" + """ + 设置电梯去向,是生命周期开始,分配目的地 + """ if 0 <= elevator_id < len(self.elevators) and 0 <= floor < len(self.floors): elevator = self.elevators[elevator_id] if immediate: self._set_elevator_target_floor(elevator, floor) - elevator.indicators.set_direction(elevator.target_floor_direction) else: elevator.next_target_floor = floor server_debug_log(f"电梯 E{elevator_id} 下一目的地设定为 F{floor}") - def elevator_set_indicators(self, elevator_id: int, up: Optional[bool] = None, down: Optional[bool] = None) -> None: - """Set elevator direction indicators""" - if 0 <= elevator_id < len(self.elevators): - elevator = self.elevators[elevator_id] - if up is not None: - elevator.indicators.up = up - if down is not None: - elevator.indicators.down = down - def get_state(self) -> SimulationStateResponse: """Get complete simulation state""" with self.lock: @@ -658,8 +649,8 @@ def step_simulation() -> Response | tuple[Response, int]: try: data: Dict[str, Any] = request.get_json() or {} ticks = data.get("ticks", 1) - server_debug_log("") - server_debug_log(f"HTTP /api/step request ----- ticks: {ticks}") + # server_debug_log("") + # server_debug_log(f"HTTP /api/step request ----- ticks: {ticks}") events = simulation.step(ticks) server_debug_log(f"HTTP /api/step response ----- tick: {simulation.tick}, events: {len(events)}\n") return json_response( @@ -693,18 +684,6 @@ def elevator_go_to_floor(elevator_id: int) -> Response | tuple[Response, int]: return json_response({"error": str(e)}, 500) -@app.route("/api/elevators//set_indicators", methods=["POST"]) -def elevator_set_indicators(elevator_id: int) -> Response | tuple[Response, int]: - try: - data: Dict[str, Any] = request.get_json() or {} - up = data.get("up") - down = data.get("down") - simulation.elevator_set_indicators(elevator_id, up, down) - return json_response({"success": True}) - except Exception as e: - return json_response({"error": str(e)}, 500) - - @app.route("/api/traffic/next", methods=["POST"]) def next_traffic_round() -> Response | tuple[Response, int]: """切换到下一个流量文件""" diff --git a/elevator_saga/traffic/down_peak.json b/elevator_saga/traffic/down_peak.json new file mode 100644 index 0000000..8e847f5 --- /dev/null +++ b/elevator_saga/traffic/down_peak.json @@ -0,0 +1,458 @@ +{ + "building": { + "floors": 6, + "elevators": 2, + "elevator_capacity": 8, + "scenario": "down_peak", + "scale": "medium", + "description": "下行高峰 - 主要从高层到底层 (medium规模)", + "expected_passengers": 74, + "duration": 200 + }, + "traffic": [ + { + "id": 1, + "origin": 2, + "destination": 0, + "tick": 0 + }, + { + "id": 2, + "origin": 2, + "destination": 1, + "tick": 3 + }, + { + "id": 3, + "origin": 4, + "destination": 0, + "tick": 5 + }, + { + "id": 4, + "origin": 3, + "destination": 0, + "tick": 6 + }, + { + "id": 5, + "origin": 5, + "destination": 0, + "tick": 7 + }, + { + "id": 6, + "origin": 4, + "destination": 0, + "tick": 9 + }, + { + "id": 7, + "origin": 1, + "destination": 0, + "tick": 10 + }, + { + "id": 8, + "origin": 5, + "destination": 0, + "tick": 12 + }, + { + "id": 9, + "origin": 3, + "destination": 0, + "tick": 13 + }, + { + "id": 10, + "origin": 3, + "destination": 0, + "tick": 14 + }, + { + "id": 11, + "origin": 2, + "destination": 0, + "tick": 15 + }, + { + "id": 12, + "origin": 1, + "destination": 0, + "tick": 17 + }, + { + "id": 13, + "origin": 3, + "destination": 2, + "tick": 18 + }, + { + "id": 14, + "origin": 4, + "destination": 0, + "tick": 19 + }, + { + "id": 15, + "origin": 3, + "destination": 0, + "tick": 22 + }, + { + "id": 16, + "origin": 5, + "destination": 4, + "tick": 28 + }, + { + "id": 17, + "origin": 1, + "destination": 0, + "tick": 30 + }, + { + "id": 18, + "origin": 3, + "destination": 0, + "tick": 32 + }, + { + "id": 19, + "origin": 4, + "destination": 0, + "tick": 35 + }, + { + "id": 20, + "origin": 4, + "destination": 0, + "tick": 37 + }, + { + "id": 21, + "origin": 1, + "destination": 0, + "tick": 39 + }, + { + "id": 22, + "origin": 5, + "destination": 0, + "tick": 40 + }, + { + "id": 23, + "origin": 5, + "destination": 0, + "tick": 41 + }, + { + "id": 24, + "origin": 5, + "destination": 0, + "tick": 42 + }, + { + "id": 25, + "origin": 2, + "destination": 1, + "tick": 44 + }, + { + "id": 26, + "origin": 4, + "destination": 0, + "tick": 45 + }, + { + "id": 27, + "origin": 1, + "destination": 0, + "tick": 46 + }, + { + "id": 28, + "origin": 3, + "destination": 0, + "tick": 49 + }, + { + "id": 29, + "origin": 5, + "destination": 0, + "tick": 50 + }, + { + "id": 30, + "origin": 5, + "destination": 0, + "tick": 51 + }, + { + "id": 31, + "origin": 4, + "destination": 3, + "tick": 52 + }, + { + "id": 32, + "origin": 3, + "destination": 1, + "tick": 53 + }, + { + "id": 33, + "origin": 3, + "destination": 0, + "tick": 55 + }, + { + "id": 34, + "origin": 5, + "destination": 0, + "tick": 58 + }, + { + "id": 35, + "origin": 3, + "destination": 0, + "tick": 59 + }, + { + "id": 36, + "origin": 4, + "destination": 3, + "tick": 60 + }, + { + "id": 37, + "origin": 5, + "destination": 0, + "tick": 62 + }, + { + "id": 38, + "origin": 4, + "destination": 0, + "tick": 63 + }, + { + "id": 39, + "origin": 4, + "destination": 0, + "tick": 65 + }, + { + "id": 40, + "origin": 2, + "destination": 0, + "tick": 71 + }, + { + "id": 41, + "origin": 4, + "destination": 0, + "tick": 73 + }, + { + "id": 42, + "origin": 4, + "destination": 0, + "tick": 74 + }, + { + "id": 43, + "origin": 3, + "destination": 0, + "tick": 79 + }, + { + "id": 44, + "origin": 5, + "destination": 1, + "tick": 83 + }, + { + "id": 45, + "origin": 3, + "destination": 0, + "tick": 84 + }, + { + "id": 46, + "origin": 1, + "destination": 0, + "tick": 91 + }, + { + "id": 47, + "origin": 3, + "destination": 0, + "tick": 94 + }, + { + "id": 48, + "origin": 2, + "destination": 0, + "tick": 97 + }, + { + "id": 49, + "origin": 3, + "destination": 0, + "tick": 99 + }, + { + "id": 50, + "origin": 3, + "destination": 0, + "tick": 104 + }, + { + "id": 51, + "origin": 5, + "destination": 0, + "tick": 105 + }, + { + "id": 52, + "origin": 5, + "destination": 0, + "tick": 111 + }, + { + "id": 53, + "origin": 1, + "destination": 0, + "tick": 115 + }, + { + "id": 54, + "origin": 2, + "destination": 0, + "tick": 116 + }, + { + "id": 55, + "origin": 5, + "destination": 0, + "tick": 119 + }, + { + "id": 56, + "origin": 5, + "destination": 0, + "tick": 122 + }, + { + "id": 57, + "origin": 3, + "destination": 2, + "tick": 124 + }, + { + "id": 58, + "origin": 3, + "destination": 2, + "tick": 127 + }, + { + "id": 59, + "origin": 2, + "destination": 0, + "tick": 129 + }, + { + "id": 60, + "origin": 3, + "destination": 0, + "tick": 130 + }, + { + "id": 61, + "origin": 5, + "destination": 0, + "tick": 136 + }, + { + "id": 62, + "origin": 5, + "destination": 0, + "tick": 138 + }, + { + "id": 63, + "origin": 2, + "destination": 0, + "tick": 148 + }, + { + "id": 64, + "origin": 2, + "destination": 0, + "tick": 150 + }, + { + "id": 65, + "origin": 5, + "destination": 0, + "tick": 153 + }, + { + "id": 66, + "origin": 3, + "destination": 2, + "tick": 158 + }, + { + "id": 67, + "origin": 5, + "destination": 0, + "tick": 165 + }, + { + "id": 68, + "origin": 3, + "destination": 0, + "tick": 166 + }, + { + "id": 69, + "origin": 2, + "destination": 0, + "tick": 168 + }, + { + "id": 70, + "origin": 5, + "destination": 0, + "tick": 171 + }, + { + "id": 71, + "origin": 3, + "destination": 0, + "tick": 172 + }, + { + "id": 72, + "origin": 4, + "destination": 0, + "tick": 179 + }, + { + "id": 73, + "origin": 5, + "destination": 0, + "tick": 183 + }, + { + "id": 74, + "origin": 1, + "destination": 0, + "tick": 196 + } + ] +} \ No newline at end of file diff --git a/elevator_saga/traffic/fire_evacuation.json b/elevator_saga/traffic/fire_evacuation.json new file mode 100644 index 0000000..6338912 --- /dev/null +++ b/elevator_saga/traffic/fire_evacuation.json @@ -0,0 +1,182 @@ +{ + "building": { + "floors": 6, + "elevators": 2, + "elevator_capacity": 8, + "scenario": "fire_evacuation", + "scale": "medium", + "description": "火警疏散 - 紧急疏散到大厅 (medium规模)", + "expected_passengers": 28, + "duration": 200 + }, + "traffic": [ + { + "id": 1, + "origin": 3, + "destination": 5, + "tick": 10 + }, + { + "id": 2, + "origin": 5, + "destination": 1, + "tick": 14 + }, + { + "id": 3, + "origin": 0, + "destination": 1, + "tick": 22 + }, + { + "id": 4, + "origin": 4, + "destination": 2, + "tick": 29 + }, + { + "id": 5, + "origin": 2, + "destination": 1, + "tick": 30 + }, + { + "id": 6, + "origin": 5, + "destination": 2, + "tick": 32 + }, + { + "id": 7, + "origin": 2, + "destination": 5, + "tick": 41 + }, + { + "id": 8, + "origin": 0, + "destination": 1, + "tick": 51 + }, + { + "id": 9, + "origin": 1, + "destination": 0, + "tick": 68 + }, + { + "id": 10, + "origin": 1, + "destination": 0, + "tick": 71 + }, + { + "id": 11, + "origin": 1, + "destination": 0, + "tick": 73 + }, + { + "id": 12, + "origin": 1, + "destination": 0, + "tick": 68 + }, + { + "id": 13, + "origin": 1, + "destination": 0, + "tick": 73 + }, + { + "id": 14, + "origin": 2, + "destination": 0, + "tick": 76 + }, + { + "id": 15, + "origin": 2, + "destination": 0, + "tick": 69 + }, + { + "id": 16, + "origin": 2, + "destination": 0, + "tick": 72 + }, + { + "id": 17, + "origin": 2, + "destination": 0, + "tick": 76 + }, + { + "id": 18, + "origin": 3, + "destination": 0, + "tick": 76 + }, + { + "id": 19, + "origin": 3, + "destination": 0, + "tick": 75 + }, + { + "id": 20, + "origin": 3, + "destination": 0, + "tick": 66 + }, + { + "id": 21, + "origin": 4, + "destination": 0, + "tick": 66 + }, + { + "id": 22, + "origin": 4, + "destination": 0, + "tick": 75 + }, + { + "id": 23, + "origin": 4, + "destination": 0, + "tick": 72 + }, + { + "id": 24, + "origin": 4, + "destination": 0, + "tick": 74 + }, + { + "id": 25, + "origin": 5, + "destination": 0, + "tick": 74 + }, + { + "id": 26, + "origin": 5, + "destination": 0, + "tick": 70 + }, + { + "id": 27, + "origin": 5, + "destination": 0, + "tick": 72 + }, + { + "id": 28, + "origin": 5, + "destination": 0, + "tick": 74 + } + ] +} \ No newline at end of file diff --git a/elevator_saga/traffic/generators.py b/elevator_saga/traffic/generators.py index dc0d8dd..2735fcf 100644 --- a/elevator_saga/traffic/generators.py +++ b/elevator_saga/traffic/generators.py @@ -806,7 +806,7 @@ def generate_traffic_file(scenario: str, output_file: str, scale: Optional[str] complete_data = {"building": building_config, "traffic": traffic_data} # 写入文件 - with open(output_file, "w") as f: + with open(output_file, "w", encoding="utf-8") as f: json.dump(complete_data, f, indent=2, ensure_ascii=False) print(f"Generated {len(traffic_data)} passengers for scenario '{scenario}' ({scale}) -> {output_file}") diff --git a/elevator_saga/traffic/high_density.json b/elevator_saga/traffic/high_density.json new file mode 100644 index 0000000..c4e821a --- /dev/null +++ b/elevator_saga/traffic/high_density.json @@ -0,0 +1,410 @@ +{ + "building": { + "floors": 6, + "elevators": 2, + "elevator_capacity": 8, + "scenario": "high_density", + "scale": "medium", + "description": "高密度流量 - 压力测试 (medium规模)", + "expected_passengers": 66, + "duration": 200 + }, + "traffic": [ + { + "id": 1, + "origin": 4, + "destination": 0, + "tick": 1 + }, + { + "id": 2, + "origin": 4, + "destination": 2, + "tick": 2 + }, + { + "id": 3, + "origin": 4, + "destination": 3, + "tick": 3 + }, + { + "id": 4, + "origin": 0, + "destination": 4, + "tick": 4 + }, + { + "id": 5, + "origin": 5, + "destination": 0, + "tick": 14 + }, + { + "id": 6, + "origin": 1, + "destination": 3, + "tick": 18 + }, + { + "id": 7, + "origin": 2, + "destination": 3, + "tick": 19 + }, + { + "id": 8, + "origin": 5, + "destination": 2, + "tick": 20 + }, + { + "id": 9, + "origin": 0, + "destination": 2, + "tick": 21 + }, + { + "id": 10, + "origin": 2, + "destination": 3, + "tick": 22 + }, + { + "id": 11, + "origin": 2, + "destination": 4, + "tick": 23 + }, + { + "id": 12, + "origin": 2, + "destination": 4, + "tick": 25 + }, + { + "id": 13, + "origin": 3, + "destination": 4, + "tick": 30 + }, + { + "id": 14, + "origin": 3, + "destination": 4, + "tick": 32 + }, + { + "id": 15, + "origin": 4, + "destination": 1, + "tick": 34 + }, + { + "id": 16, + "origin": 2, + "destination": 3, + "tick": 35 + }, + { + "id": 17, + "origin": 5, + "destination": 0, + "tick": 36 + }, + { + "id": 18, + "origin": 1, + "destination": 2, + "tick": 38 + }, + { + "id": 19, + "origin": 0, + "destination": 4, + "tick": 40 + }, + { + "id": 20, + "origin": 0, + "destination": 1, + "tick": 50 + }, + { + "id": 21, + "origin": 1, + "destination": 2, + "tick": 54 + }, + { + "id": 22, + "origin": 5, + "destination": 1, + "tick": 60 + }, + { + "id": 23, + "origin": 3, + "destination": 1, + "tick": 61 + }, + { + "id": 24, + "origin": 3, + "destination": 1, + "tick": 63 + }, + { + "id": 25, + "origin": 0, + "destination": 1, + "tick": 67 + }, + { + "id": 26, + "origin": 0, + "destination": 4, + "tick": 70 + }, + { + "id": 27, + "origin": 5, + "destination": 3, + "tick": 76 + }, + { + "id": 28, + "origin": 2, + "destination": 4, + "tick": 78 + }, + { + "id": 29, + "origin": 2, + "destination": 3, + "tick": 80 + }, + { + "id": 30, + "origin": 5, + "destination": 1, + "tick": 91 + }, + { + "id": 31, + "origin": 3, + "destination": 1, + "tick": 92 + }, + { + "id": 32, + "origin": 3, + "destination": 4, + "tick": 95 + }, + { + "id": 33, + "origin": 0, + "destination": 1, + "tick": 96 + }, + { + "id": 34, + "origin": 2, + "destination": 5, + "tick": 98 + }, + { + "id": 35, + "origin": 2, + "destination": 0, + "tick": 100 + }, + { + "id": 36, + "origin": 1, + "destination": 4, + "tick": 103 + }, + { + "id": 37, + "origin": 2, + "destination": 5, + "tick": 110 + }, + { + "id": 38, + "origin": 3, + "destination": 5, + "tick": 112 + }, + { + "id": 39, + "origin": 0, + "destination": 5, + "tick": 113 + }, + { + "id": 40, + "origin": 3, + "destination": 1, + "tick": 114 + }, + { + "id": 41, + "origin": 0, + "destination": 5, + "tick": 122 + }, + { + "id": 42, + "origin": 2, + "destination": 5, + "tick": 127 + }, + { + "id": 43, + "origin": 1, + "destination": 4, + "tick": 130 + }, + { + "id": 44, + "origin": 4, + "destination": 2, + "tick": 137 + }, + { + "id": 45, + "origin": 3, + "destination": 4, + "tick": 139 + }, + { + "id": 46, + "origin": 4, + "destination": 0, + "tick": 140 + }, + { + "id": 47, + "origin": 4, + "destination": 2, + "tick": 143 + }, + { + "id": 48, + "origin": 2, + "destination": 4, + "tick": 144 + }, + { + "id": 49, + "origin": 2, + "destination": 5, + "tick": 147 + }, + { + "id": 50, + "origin": 5, + "destination": 1, + "tick": 148 + }, + { + "id": 51, + "origin": 1, + "destination": 3, + "tick": 149 + }, + { + "id": 52, + "origin": 4, + "destination": 1, + "tick": 160 + }, + { + "id": 53, + "origin": 4, + "destination": 0, + "tick": 172 + }, + { + "id": 54, + "origin": 3, + "destination": 1, + "tick": 173 + }, + { + "id": 55, + "origin": 5, + "destination": 4, + "tick": 174 + }, + { + "id": 56, + "origin": 0, + "destination": 4, + "tick": 179 + }, + { + "id": 57, + "origin": 4, + "destination": 2, + "tick": 180 + }, + { + "id": 58, + "origin": 2, + "destination": 1, + "tick": 182 + }, + { + "id": 59, + "origin": 1, + "destination": 0, + "tick": 186 + }, + { + "id": 60, + "origin": 2, + "destination": 0, + "tick": 188 + }, + { + "id": 61, + "origin": 4, + "destination": 5, + "tick": 191 + }, + { + "id": 62, + "origin": 5, + "destination": 1, + "tick": 192 + }, + { + "id": 63, + "origin": 1, + "destination": 4, + "tick": 193 + }, + { + "id": 64, + "origin": 0, + "destination": 3, + "tick": 195 + }, + { + "id": 65, + "origin": 4, + "destination": 2, + "tick": 196 + }, + { + "id": 66, + "origin": 5, + "destination": 0, + "tick": 197 + } + ] +} \ No newline at end of file diff --git a/elevator_saga/traffic/inter_floor.json b/elevator_saga/traffic/inter_floor.json index a58f640..ba90f9b 100644 --- a/elevator_saga/traffic/inter_floor.json +++ b/elevator_saga/traffic/inter_floor.json @@ -1,152 +1,374 @@ { "building": { - "floors": 12, - "elevators": 4, + "floors": 6, + "elevators": 2, "elevator_capacity": 8, "scenario": "inter_floor", - "scale": "large", - "description": "楼层间流量 - 适合小建筑 (large规模)", - "expected_passengers": 76, - "duration": 100 + "scale": "medium", + "description": "楼层间流量 - 适合小建筑 (medium规模)", + "expected_passengers": 60, + "duration": 200 }, "traffic": [ { "id": 1, - "origin": 0, + "origin": 5, "destination": 1, - "tick": 1 + "tick": 0 }, { "id": 2, - "origin": 2, - "destination": 6, - "tick": 11 + "origin": 5, + "destination": 1, + "tick": 5 }, { "id": 3, - "origin": 11, - "destination": 3, - "tick": 12 + "origin": 1, + "destination": 5, + "tick": 9 }, { "id": 4, - "origin": 11, - "destination": 10, - "tick": 13 + "origin": 1, + "destination": 3, + "tick": 11 }, { "id": 5, - "origin": 1, - "destination": 4, - "tick": 20 + "origin": 5, + "destination": 1, + "tick": 13 }, { "id": 6, - "origin": 1, - "destination": 2, - "tick": 22 + "origin": 2, + "destination": 3, + "tick": 17 }, { "id": 7, - "origin": 6, - "destination": 3, - "tick": 26 + "origin": 3, + "destination": 2, + "tick": 18 }, { "id": 8, - "origin": 8, - "destination": 9, - "tick": 27 + "origin": 4, + "destination": 3, + "tick": 19 }, { "id": 9, - "origin": 1, - "destination": 8, - "tick": 28 + "origin": 4, + "destination": 1, + "tick": 20 }, { "id": 10, - "origin": 9, - "destination": 3, - "tick": 40 + "origin": 4, + "destination": 1, + "tick": 21 }, { "id": 11, - "origin": 9, - "destination": 3, - "tick": 52 + "origin": 3, + "destination": 1, + "tick": 22 }, { "id": 12, - "origin": 8, - "destination": 10, - "tick": 57 + "origin": 4, + "destination": 3, + "tick": 24 }, { "id": 13, - "origin": 10, - "destination": 9, - "tick": 58 + "origin": 4, + "destination": 1, + "tick": 25 }, { "id": 14, "origin": 4, - "destination": 1, - "tick": 60 + "destination": 3, + "tick": 26 }, { "id": 15, - "origin": 7, - "destination": 8, - "tick": 65 + "origin": 4, + "destination": 2, + "tick": 28 }, { "id": 16, - "origin": 11, - "destination": 1, - "tick": 68 + "origin": 3, + "destination": 2, + "tick": 29 }, { "id": 17, "origin": 5, - "destination": 7, - "tick": 69 + "destination": 3, + "tick": 34 }, { "id": 18, - "origin": 8, - "destination": 9, - "tick": 71 + "origin": 3, + "destination": 1, + "tick": 36 }, { "id": 19, "origin": 3, - "destination": 9, - "tick": 75 + "destination": 2, + "tick": 38 }, { "id": 20, - "origin": 3, - "destination": 8, - "tick": 90 + "origin": 2, + "destination": 5, + "tick": 43 }, { "id": 21, - "origin": 2, - "destination": 4, - "tick": 91 + "origin": 3, + "destination": 2, + "tick": 46 }, { "id": 22, - "origin": 8, - "destination": 9, - "tick": 94 + "origin": 4, + "destination": 1, + "tick": 51 }, { "id": 23, "origin": 5, - "destination": 8, - "tick": 97 + "destination": 4, + "tick": 52 + }, + { + "id": 24, + "origin": 5, + "destination": 1, + "tick": 54 + }, + { + "id": 25, + "origin": 1, + "destination": 4, + "tick": 58 + }, + { + "id": 26, + "origin": 4, + "destination": 3, + "tick": 62 + }, + { + "id": 27, + "origin": 2, + "destination": 3, + "tick": 63 + }, + { + "id": 28, + "origin": 2, + "destination": 4, + "tick": 66 + }, + { + "id": 29, + "origin": 3, + "destination": 5, + "tick": 68 + }, + { + "id": 30, + "origin": 4, + "destination": 1, + "tick": 69 + }, + { + "id": 31, + "origin": 1, + "destination": 2, + "tick": 71 + }, + { + "id": 32, + "origin": 1, + "destination": 3, + "tick": 79 + }, + { + "id": 33, + "origin": 5, + "destination": 1, + "tick": 81 + }, + { + "id": 34, + "origin": 4, + "destination": 1, + "tick": 83 + }, + { + "id": 35, + "origin": 5, + "destination": 2, + "tick": 90 + }, + { + "id": 36, + "origin": 2, + "destination": 1, + "tick": 93 + }, + { + "id": 37, + "origin": 2, + "destination": 4, + "tick": 94 + }, + { + "id": 38, + "origin": 2, + "destination": 3, + "tick": 102 + }, + { + "id": 39, + "origin": 5, + "destination": 1, + "tick": 105 + }, + { + "id": 40, + "origin": 4, + "destination": 5, + "tick": 106 + }, + { + "id": 41, + "origin": 3, + "destination": 4, + "tick": 109 + }, + { + "id": 42, + "origin": 4, + "destination": 3, + "tick": 122 + }, + { + "id": 43, + "origin": 5, + "destination": 2, + "tick": 127 + }, + { + "id": 44, + "origin": 5, + "destination": 2, + "tick": 130 + }, + { + "id": 45, + "origin": 1, + "destination": 2, + "tick": 135 + }, + { + "id": 46, + "origin": 5, + "destination": 3, + "tick": 139 + }, + { + "id": 47, + "origin": 1, + "destination": 5, + "tick": 144 + }, + { + "id": 48, + "origin": 5, + "destination": 2, + "tick": 146 + }, + { + "id": 49, + "origin": 3, + "destination": 4, + "tick": 151 + }, + { + "id": 50, + "origin": 2, + "destination": 4, + "tick": 155 + }, + { + "id": 51, + "origin": 3, + "destination": 5, + "tick": 157 + }, + { + "id": 52, + "origin": 5, + "destination": 2, + "tick": 163 + }, + { + "id": 53, + "origin": 3, + "destination": 1, + "tick": 172 + }, + { + "id": 54, + "origin": 2, + "destination": 4, + "tick": 174 + }, + { + "id": 55, + "origin": 4, + "destination": 1, + "tick": 179 + }, + { + "id": 56, + "origin": 3, + "destination": 4, + "tick": 186 + }, + { + "id": 57, + "origin": 5, + "destination": 2, + "tick": 189 + }, + { + "id": 58, + "origin": 1, + "destination": 4, + "tick": 190 + }, + { + "id": 59, + "origin": 5, + "destination": 2, + "tick": 194 + }, + { + "id": 60, + "origin": 2, + "destination": 4, + "tick": 197 } ] } \ No newline at end of file diff --git a/elevator_saga/traffic/lunch_rush.json b/elevator_saga/traffic/lunch_rush.json new file mode 100644 index 0000000..60f8ae2 --- /dev/null +++ b/elevator_saga/traffic/lunch_rush.json @@ -0,0 +1,164 @@ +{ + "building": { + "floors": 6, + "elevators": 2, + "elevator_capacity": 8, + "scenario": "lunch_rush", + "scale": "medium", + "description": "午餐时间流量 - 双向流量,适合中大型建筑 (medium规模)", + "expected_passengers": 25, + "duration": 200 + }, + "traffic": [ + { + "id": 1, + "origin": 4, + "destination": 2, + "tick": 4 + }, + { + "id": 2, + "origin": 4, + "destination": 2, + "tick": 21 + }, + { + "id": 3, + "origin": 3, + "destination": 2, + "tick": 44 + }, + { + "id": 4, + "origin": 1, + "destination": 3, + "tick": 55 + }, + { + "id": 5, + "origin": 1, + "destination": 3, + "tick": 63 + }, + { + "id": 6, + "origin": 1, + "destination": 4, + "tick": 64 + }, + { + "id": 7, + "origin": 1, + "destination": 4, + "tick": 72 + }, + { + "id": 8, + "origin": 1, + "destination": 4, + "tick": 75 + }, + { + "id": 9, + "origin": 2, + "destination": 4, + "tick": 78 + }, + { + "id": 10, + "origin": 5, + "destination": 1, + "tick": 80 + }, + { + "id": 11, + "origin": 4, + "destination": 2, + "tick": 82 + }, + { + "id": 12, + "origin": 5, + "destination": 1, + "tick": 85 + }, + { + "id": 13, + "origin": 5, + "destination": 2, + "tick": 93 + }, + { + "id": 14, + "origin": 2, + "destination": 3, + "tick": 96 + }, + { + "id": 15, + "origin": 2, + "destination": 3, + "tick": 97 + }, + { + "id": 16, + "origin": 2, + "destination": 5, + "tick": 104 + }, + { + "id": 17, + "origin": 3, + "destination": 1, + "tick": 107 + }, + { + "id": 18, + "origin": 2, + "destination": 3, + "tick": 111 + }, + { + "id": 19, + "origin": 3, + "destination": 1, + "tick": 113 + }, + { + "id": 20, + "origin": 2, + "destination": 3, + "tick": 114 + }, + { + "id": 21, + "origin": 3, + "destination": 2, + "tick": 116 + }, + { + "id": 22, + "origin": 2, + "destination": 3, + "tick": 127 + }, + { + "id": 23, + "origin": 2, + "destination": 3, + "tick": 139 + }, + { + "id": 24, + "origin": 2, + "destination": 5, + "tick": 166 + }, + { + "id": 25, + "origin": 4, + "destination": 1, + "tick": 192 + } + ] +} \ No newline at end of file diff --git a/elevator_saga/traffic/medical.json b/elevator_saga/traffic/medical.json new file mode 100644 index 0000000..c755eb7 --- /dev/null +++ b/elevator_saga/traffic/medical.json @@ -0,0 +1,494 @@ +{ + "building": { + "floors": 6, + "elevators": 2, + "elevator_capacity": 8, + "scenario": "medical", + "scale": "medium", + "description": "医疗建筑 - 特殊流量模式 (medium规模)", + "expected_passengers": 80, + "duration": 200 + }, + "traffic": [ + { + "id": 1, + "origin": 1, + "destination": 0, + "tick": 0 + }, + { + "id": 2, + "origin": 0, + "destination": 1, + "tick": 3 + }, + { + "id": 3, + "origin": 4, + "destination": 0, + "tick": 4 + }, + { + "id": 4, + "origin": 0, + "destination": 1, + "tick": 7 + }, + { + "id": 5, + "origin": 3, + "destination": 0, + "tick": 8 + }, + { + "id": 6, + "origin": 0, + "destination": 2, + "tick": 9 + }, + { + "id": 7, + "origin": 0, + "destination": 3, + "tick": 12 + }, + { + "id": 8, + "origin": 0, + "destination": 1, + "tick": 13 + }, + { + "id": 9, + "origin": 2, + "destination": 0, + "tick": 16 + }, + { + "id": 10, + "origin": 1, + "destination": 3, + "tick": 17 + }, + { + "id": 11, + "origin": 0, + "destination": 3, + "tick": 21 + }, + { + "id": 12, + "origin": 0, + "destination": 3, + "tick": 24 + }, + { + "id": 13, + "origin": 0, + "destination": 1, + "tick": 30 + }, + { + "id": 14, + "origin": 0, + "destination": 4, + "tick": 31 + }, + { + "id": 15, + "origin": 0, + "destination": 1, + "tick": 32 + }, + { + "id": 16, + "origin": 0, + "destination": 1, + "tick": 34 + }, + { + "id": 17, + "origin": 4, + "destination": 0, + "tick": 35 + }, + { + "id": 18, + "origin": 0, + "destination": 4, + "tick": 36 + }, + { + "id": 19, + "origin": 0, + "destination": 2, + "tick": 37 + }, + { + "id": 20, + "origin": 0, + "destination": 1, + "tick": 38 + }, + { + "id": 21, + "origin": 4, + "destination": 0, + "tick": 39 + }, + { + "id": 22, + "origin": 0, + "destination": 3, + "tick": 40 + }, + { + "id": 23, + "origin": 0, + "destination": 1, + "tick": 41 + }, + { + "id": 24, + "origin": 1, + "destination": 0, + "tick": 43 + }, + { + "id": 25, + "origin": 1, + "destination": 0, + "tick": 45 + }, + { + "id": 26, + "origin": 0, + "destination": 1, + "tick": 48 + }, + { + "id": 27, + "origin": 0, + "destination": 4, + "tick": 49 + }, + { + "id": 28, + "origin": 0, + "destination": 2, + "tick": 50 + }, + { + "id": 29, + "origin": 0, + "destination": 2, + "tick": 51 + }, + { + "id": 30, + "origin": 0, + "destination": 2, + "tick": 52 + }, + { + "id": 31, + "origin": 0, + "destination": 1, + "tick": 53 + }, + { + "id": 32, + "origin": 1, + "destination": 0, + "tick": 55 + }, + { + "id": 33, + "origin": 0, + "destination": 2, + "tick": 56 + }, + { + "id": 34, + "origin": 2, + "destination": 0, + "tick": 57 + }, + { + "id": 35, + "origin": 0, + "destination": 2, + "tick": 63 + }, + { + "id": 36, + "origin": 0, + "destination": 3, + "tick": 64 + }, + { + "id": 37, + "origin": 0, + "destination": 1, + "tick": 65 + }, + { + "id": 38, + "origin": 0, + "destination": 2, + "tick": 66 + }, + { + "id": 39, + "origin": 1, + "destination": 0, + "tick": 68 + }, + { + "id": 40, + "origin": 1, + "destination": 0, + "tick": 69 + }, + { + "id": 41, + "origin": 0, + "destination": 2, + "tick": 70 + }, + { + "id": 42, + "origin": 1, + "destination": 3, + "tick": 72 + }, + { + "id": 43, + "origin": 4, + "destination": 0, + "tick": 73 + }, + { + "id": 44, + "origin": 0, + "destination": 2, + "tick": 74 + }, + { + "id": 45, + "origin": 2, + "destination": 0, + "tick": 75 + }, + { + "id": 46, + "origin": 0, + "destination": 1, + "tick": 79 + }, + { + "id": 47, + "origin": 1, + "destination": 0, + "tick": 80 + }, + { + "id": 48, + "origin": 0, + "destination": 1, + "tick": 83 + }, + { + "id": 49, + "origin": 0, + "destination": 1, + "tick": 84 + }, + { + "id": 50, + "origin": 0, + "destination": 1, + "tick": 85 + }, + { + "id": 51, + "origin": 5, + "destination": 3, + "tick": 86 + }, + { + "id": 52, + "origin": 0, + "destination": 1, + "tick": 87 + }, + { + "id": 53, + "origin": 1, + "destination": 0, + "tick": 88 + }, + { + "id": 54, + "origin": 0, + "destination": 5, + "tick": 89 + }, + { + "id": 55, + "origin": 0, + "destination": 4, + "tick": 91 + }, + { + "id": 56, + "origin": 0, + "destination": 5, + "tick": 92 + }, + { + "id": 57, + "origin": 3, + "destination": 0, + "tick": 93 + }, + { + "id": 58, + "origin": 1, + "destination": 0, + "tick": 94 + }, + { + "id": 59, + "origin": 2, + "destination": 0, + "tick": 95 + }, + { + "id": 60, + "origin": 1, + "destination": 0, + "tick": 98 + }, + { + "id": 61, + "origin": 2, + "destination": 0, + "tick": 104 + }, + { + "id": 62, + "origin": 1, + "destination": 0, + "tick": 105 + }, + { + "id": 63, + "origin": 0, + "destination": 4, + "tick": 106 + }, + { + "id": 64, + "origin": 2, + "destination": 0, + "tick": 107 + }, + { + "id": 65, + "origin": 2, + "destination": 0, + "tick": 108 + }, + { + "id": 66, + "origin": 0, + "destination": 1, + "tick": 109 + }, + { + "id": 67, + "origin": 1, + "destination": 0, + "tick": 110 + }, + { + "id": 68, + "origin": 0, + "destination": 2, + "tick": 112 + }, + { + "id": 69, + "origin": 1, + "destination": 0, + "tick": 114 + }, + { + "id": 70, + "origin": 0, + "destination": 4, + "tick": 117 + }, + { + "id": 71, + "origin": 0, + "destination": 1, + "tick": 118 + }, + { + "id": 72, + "origin": 0, + "destination": 3, + "tick": 119 + }, + { + "id": 73, + "origin": 0, + "destination": 2, + "tick": 120 + }, + { + "id": 74, + "origin": 2, + "destination": 0, + "tick": 122 + }, + { + "id": 75, + "origin": 0, + "destination": 2, + "tick": 128 + }, + { + "id": 76, + "origin": 0, + "destination": 4, + "tick": 129 + }, + { + "id": 77, + "origin": 0, + "destination": 2, + "tick": 132 + }, + { + "id": 78, + "origin": 3, + "destination": 5, + "tick": 135 + }, + { + "id": 79, + "origin": 0, + "destination": 3, + "tick": 137 + }, + { + "id": 80, + "origin": 0, + "destination": 4, + "tick": 138 + } + ] +} \ No newline at end of file diff --git a/elevator_saga/traffic/meeting_event.json b/elevator_saga/traffic/meeting_event.json new file mode 100644 index 0000000..72d5e01 --- /dev/null +++ b/elevator_saga/traffic/meeting_event.json @@ -0,0 +1,314 @@ +{ + "building": { + "floors": 6, + "elevators": 2, + "elevator_capacity": 8, + "scenario": "meeting_event", + "scale": "medium", + "description": "会议事件 - 集中到达和离开 (medium规模)", + "expected_passengers": 50, + "duration": 200 + }, + "traffic": [ + { + "id": 1, + "origin": 0, + "destination": 3, + "tick": 0 + }, + { + "id": 2, + "origin": 0, + "destination": 3, + "tick": 1 + }, + { + "id": 3, + "origin": 0, + "destination": 3, + "tick": 2 + }, + { + "id": 4, + "origin": 0, + "destination": 3, + "tick": 3 + }, + { + "id": 5, + "origin": 0, + "destination": 3, + "tick": 4 + }, + { + "id": 6, + "origin": 0, + "destination": 3, + "tick": 5 + }, + { + "id": 7, + "origin": 3, + "destination": 1, + "tick": 6 + }, + { + "id": 8, + "origin": 0, + "destination": 3, + "tick": 7 + }, + { + "id": 9, + "origin": 0, + "destination": 3, + "tick": 8 + }, + { + "id": 10, + "origin": 0, + "destination": 1, + "tick": 9 + }, + { + "id": 11, + "origin": 0, + "destination": 3, + "tick": 10 + }, + { + "id": 12, + "origin": 0, + "destination": 3, + "tick": 11 + }, + { + "id": 13, + "origin": 0, + "destination": 3, + "tick": 12 + }, + { + "id": 14, + "origin": 0, + "destination": 3, + "tick": 13 + }, + { + "id": 15, + "origin": 0, + "destination": 3, + "tick": 14 + }, + { + "id": 16, + "origin": 0, + "destination": 3, + "tick": 15 + }, + { + "id": 17, + "origin": 0, + "destination": 3, + "tick": 16 + }, + { + "id": 18, + "origin": 0, + "destination": 3, + "tick": 17 + }, + { + "id": 19, + "origin": 0, + "destination": 3, + "tick": 18 + }, + { + "id": 20, + "origin": 0, + "destination": 3, + "tick": 19 + }, + { + "id": 21, + "origin": 0, + "destination": 3, + "tick": 20 + }, + { + "id": 22, + "origin": 0, + "destination": 3, + "tick": 21 + }, + { + "id": 23, + "origin": 1, + "destination": 2, + "tick": 22 + }, + { + "id": 24, + "origin": 0, + "destination": 3, + "tick": 23 + }, + { + "id": 25, + "origin": 0, + "destination": 3, + "tick": 24 + }, + { + "id": 26, + "origin": 0, + "destination": 3, + "tick": 25 + }, + { + "id": 27, + "origin": 0, + "destination": 3, + "tick": 26 + }, + { + "id": 28, + "origin": 0, + "destination": 3, + "tick": 27 + }, + { + "id": 29, + "origin": 0, + "destination": 3, + "tick": 28 + }, + { + "id": 30, + "origin": 0, + "destination": 3, + "tick": 29 + }, + { + "id": 31, + "origin": 0, + "destination": 3, + "tick": 30 + }, + { + "id": 32, + "origin": 0, + "destination": 3, + "tick": 31 + }, + { + "id": 33, + "origin": 0, + "destination": 3, + "tick": 32 + }, + { + "id": 34, + "origin": 0, + "destination": 3, + "tick": 33 + }, + { + "id": 35, + "origin": 0, + "destination": 3, + "tick": 34 + }, + { + "id": 36, + "origin": 4, + "destination": 2, + "tick": 35 + }, + { + "id": 37, + "origin": 0, + "destination": 3, + "tick": 36 + }, + { + "id": 38, + "origin": 0, + "destination": 3, + "tick": 37 + }, + { + "id": 39, + "origin": 0, + "destination": 3, + "tick": 38 + }, + { + "id": 40, + "origin": 0, + "destination": 3, + "tick": 39 + }, + { + "id": 41, + "origin": 0, + "destination": 3, + "tick": 40 + }, + { + "id": 42, + "origin": 0, + "destination": 3, + "tick": 41 + }, + { + "id": 43, + "origin": 0, + "destination": 3, + "tick": 42 + }, + { + "id": 44, + "origin": 0, + "destination": 3, + "tick": 43 + }, + { + "id": 45, + "origin": 0, + "destination": 3, + "tick": 44 + }, + { + "id": 46, + "origin": 5, + "destination": 2, + "tick": 45 + }, + { + "id": 47, + "origin": 0, + "destination": 3, + "tick": 46 + }, + { + "id": 48, + "origin": 0, + "destination": 3, + "tick": 47 + }, + { + "id": 49, + "origin": 0, + "destination": 5, + "tick": 48 + }, + { + "id": 50, + "origin": 0, + "destination": 3, + "tick": 49 + } + ] +} \ No newline at end of file diff --git a/elevator_saga/traffic/mixed_scenario.json b/elevator_saga/traffic/mixed_scenario.json new file mode 100644 index 0000000..8680bc9 --- /dev/null +++ b/elevator_saga/traffic/mixed_scenario.json @@ -0,0 +1,614 @@ +{ + "building": { + "floors": 6, + "elevators": 2, + "elevator_capacity": 8, + "scenario": "mixed_scenario", + "scale": "medium", + "description": "混合场景 - 包含多种流量模式,适合中大型建筑 (medium规模)", + "expected_passengers": 100, + "duration": 200 + }, + "traffic": [ + { + "id": 1, + "origin": 0, + "destination": 4, + "tick": 0 + }, + { + "id": 2, + "origin": 0, + "destination": 5, + "tick": 2 + }, + { + "id": 3, + "origin": 0, + "destination": 2, + "tick": 4 + }, + { + "id": 4, + "origin": 0, + "destination": 4, + "tick": 6 + }, + { + "id": 5, + "origin": 0, + "destination": 1, + "tick": 11 + }, + { + "id": 6, + "origin": 0, + "destination": 2, + "tick": 12 + }, + { + "id": 7, + "origin": 3, + "destination": 4, + "tick": 14 + }, + { + "id": 8, + "origin": 0, + "destination": 4, + "tick": 15 + }, + { + "id": 9, + "origin": 0, + "destination": 2, + "tick": 16 + }, + { + "id": 10, + "origin": 0, + "destination": 2, + "tick": 17 + }, + { + "id": 11, + "origin": 0, + "destination": 3, + "tick": 21 + }, + { + "id": 12, + "origin": 0, + "destination": 4, + "tick": 22 + }, + { + "id": 13, + "origin": 0, + "destination": 1, + "tick": 23 + }, + { + "id": 14, + "origin": 0, + "destination": 4, + "tick": 25 + }, + { + "id": 15, + "origin": 0, + "destination": 1, + "tick": 27 + }, + { + "id": 16, + "origin": 0, + "destination": 3, + "tick": 28 + }, + { + "id": 17, + "origin": 0, + "destination": 1, + "tick": 29 + }, + { + "id": 18, + "origin": 0, + "destination": 3, + "tick": 31 + }, + { + "id": 19, + "origin": 0, + "destination": 3, + "tick": 32 + }, + { + "id": 20, + "origin": 0, + "destination": 1, + "tick": 40 + }, + { + "id": 21, + "origin": 0, + "destination": 3, + "tick": 41 + }, + { + "id": 22, + "origin": 0, + "destination": 1, + "tick": 42 + }, + { + "id": 23, + "origin": 0, + "destination": 5, + "tick": 43 + }, + { + "id": 24, + "origin": 0, + "destination": 2, + "tick": 44 + }, + { + "id": 25, + "origin": 0, + "destination": 4, + "tick": 49 + }, + { + "id": 26, + "origin": 0, + "destination": 1, + "tick": 50 + }, + { + "id": 27, + "origin": 2, + "destination": 1, + "tick": 52 + }, + { + "id": 28, + "origin": 2, + "destination": 3, + "tick": 53 + }, + { + "id": 29, + "origin": 4, + "destination": 0, + "tick": 54 + }, + { + "id": 30, + "origin": 5, + "destination": 2, + "tick": 55 + }, + { + "id": 31, + "origin": 5, + "destination": 1, + "tick": 57 + }, + { + "id": 32, + "origin": 2, + "destination": 5, + "tick": 58 + }, + { + "id": 33, + "origin": 0, + "destination": 1, + "tick": 66 + }, + { + "id": 34, + "origin": 3, + "destination": 1, + "tick": 68 + }, + { + "id": 35, + "origin": 2, + "destination": 5, + "tick": 72 + }, + { + "id": 36, + "origin": 4, + "destination": 2, + "tick": 73 + }, + { + "id": 37, + "origin": 0, + "destination": 4, + "tick": 74 + }, + { + "id": 38, + "origin": 3, + "destination": 0, + "tick": 75 + }, + { + "id": 39, + "origin": 3, + "destination": 2, + "tick": 80 + }, + { + "id": 40, + "origin": 3, + "destination": 4, + "tick": 81 + }, + { + "id": 41, + "origin": 0, + "destination": 4, + "tick": 85 + }, + { + "id": 42, + "origin": 4, + "destination": 2, + "tick": 87 + }, + { + "id": 43, + "origin": 3, + "destination": 5, + "tick": 90 + }, + { + "id": 44, + "origin": 5, + "destination": 1, + "tick": 91 + }, + { + "id": 45, + "origin": 2, + "destination": 4, + "tick": 92 + }, + { + "id": 46, + "origin": 3, + "destination": 0, + "tick": 93 + }, + { + "id": 47, + "origin": 3, + "destination": 5, + "tick": 95 + }, + { + "id": 48, + "origin": 0, + "destination": 5, + "tick": 97 + }, + { + "id": 49, + "origin": 2, + "destination": 5, + "tick": 98 + }, + { + "id": 50, + "origin": 3, + "destination": 2, + "tick": 100 + }, + { + "id": 51, + "origin": 1, + "destination": 5, + "tick": 101 + }, + { + "id": 52, + "origin": 3, + "destination": 1, + "tick": 102 + }, + { + "id": 53, + "origin": 0, + "destination": 3, + "tick": 103 + }, + { + "id": 54, + "origin": 3, + "destination": 1, + "tick": 104 + }, + { + "id": 55, + "origin": 2, + "destination": 5, + "tick": 106 + }, + { + "id": 56, + "origin": 5, + "destination": 4, + "tick": 107 + }, + { + "id": 57, + "origin": 5, + "destination": 2, + "tick": 108 + }, + { + "id": 58, + "origin": 3, + "destination": 2, + "tick": 109 + }, + { + "id": 59, + "origin": 2, + "destination": 4, + "tick": 110 + }, + { + "id": 60, + "origin": 4, + "destination": 2, + "tick": 111 + }, + { + "id": 61, + "origin": 0, + "destination": 5, + "tick": 112 + }, + { + "id": 62, + "origin": 2, + "destination": 4, + "tick": 114 + }, + { + "id": 63, + "origin": 4, + "destination": 2, + "tick": 115 + }, + { + "id": 64, + "origin": 4, + "destination": 2, + "tick": 116 + }, + { + "id": 65, + "origin": 1, + "destination": 4, + "tick": 117 + }, + { + "id": 66, + "origin": 2, + "destination": 3, + "tick": 118 + }, + { + "id": 67, + "origin": 5, + "destination": 1, + "tick": 119 + }, + { + "id": 68, + "origin": 0, + "destination": 3, + "tick": 121 + }, + { + "id": 69, + "origin": 3, + "destination": 5, + "tick": 123 + }, + { + "id": 70, + "origin": 1, + "destination": 3, + "tick": 124 + }, + { + "id": 71, + "origin": 5, + "destination": 3, + "tick": 125 + }, + { + "id": 72, + "origin": 5, + "destination": 1, + "tick": 126 + }, + { + "id": 73, + "origin": 5, + "destination": 1, + "tick": 127 + }, + { + "id": 74, + "origin": 3, + "destination": 1, + "tick": 129 + }, + { + "id": 75, + "origin": 3, + "destination": 2, + "tick": 130 + }, + { + "id": 76, + "origin": 2, + "destination": 1, + "tick": 131 + }, + { + "id": 77, + "origin": 3, + "destination": 0, + "tick": 132 + }, + { + "id": 78, + "origin": 3, + "destination": 0, + "tick": 139 + }, + { + "id": 79, + "origin": 4, + "destination": 0, + "tick": 143 + }, + { + "id": 80, + "origin": 2, + "destination": 1, + "tick": 144 + }, + { + "id": 81, + "origin": 2, + "destination": 0, + "tick": 145 + }, + { + "id": 82, + "origin": 1, + "destination": 0, + "tick": 148 + }, + { + "id": 83, + "origin": 3, + "destination": 1, + "tick": 150 + }, + { + "id": 84, + "origin": 3, + "destination": 0, + "tick": 152 + }, + { + "id": 85, + "origin": 3, + "destination": 0, + "tick": 153 + }, + { + "id": 86, + "origin": 4, + "destination": 0, + "tick": 154 + }, + { + "id": 87, + "origin": 4, + "destination": 0, + "tick": 159 + }, + { + "id": 88, + "origin": 3, + "destination": 0, + "tick": 160 + }, + { + "id": 89, + "origin": 2, + "destination": 0, + "tick": 161 + }, + { + "id": 90, + "origin": 2, + "destination": 1, + "tick": 162 + }, + { + "id": 91, + "origin": 2, + "destination": 0, + "tick": 164 + }, + { + "id": 92, + "origin": 3, + "destination": 1, + "tick": 166 + }, + { + "id": 93, + "origin": 5, + "destination": 0, + "tick": 169 + }, + { + "id": 94, + "origin": 2, + "destination": 0, + "tick": 171 + }, + { + "id": 95, + "origin": 3, + "destination": 0, + "tick": 173 + }, + { + "id": 96, + "origin": 5, + "destination": 0, + "tick": 175 + }, + { + "id": 97, + "origin": 3, + "destination": 0, + "tick": 176 + }, + { + "id": 98, + "origin": 5, + "destination": 0, + "tick": 178 + }, + { + "id": 99, + "origin": 5, + "destination": 0, + "tick": 181 + }, + { + "id": 100, + "origin": 2, + "destination": 0, + "tick": 190 + } + ] +} \ No newline at end of file diff --git a/elevator_saga/traffic/progressive_test.json b/elevator_saga/traffic/progressive_test.json new file mode 100644 index 0000000..4d70d35 --- /dev/null +++ b/elevator_saga/traffic/progressive_test.json @@ -0,0 +1,590 @@ +{ + "building": { + "floors": 6, + "elevators": 2, + "elevator_capacity": 8, + "scenario": "progressive_test", + "scale": "medium", + "description": "渐进式测试 - 强度逐渐增加 (medium规模)", + "expected_passengers": 96, + "duration": 200 + }, + "traffic": [ + { + "id": 1, + "origin": 1, + "destination": 0, + "tick": 2 + }, + { + "id": 2, + "origin": 0, + "destination": 3, + "tick": 4 + }, + { + "id": 3, + "origin": 0, + "destination": 5, + "tick": 10 + }, + { + "id": 4, + "origin": 0, + "destination": 1, + "tick": 11 + }, + { + "id": 5, + "origin": 0, + "destination": 1, + "tick": 13 + }, + { + "id": 6, + "origin": 2, + "destination": 1, + "tick": 14 + }, + { + "id": 7, + "origin": 3, + "destination": 5, + "tick": 16 + }, + { + "id": 8, + "origin": 1, + "destination": 4, + "tick": 17 + }, + { + "id": 9, + "origin": 5, + "destination": 3, + "tick": 18 + }, + { + "id": 10, + "origin": 3, + "destination": 4, + "tick": 22 + }, + { + "id": 11, + "origin": 5, + "destination": 0, + "tick": 23 + }, + { + "id": 12, + "origin": 0, + "destination": 5, + "tick": 24 + }, + { + "id": 13, + "origin": 4, + "destination": 5, + "tick": 26 + }, + { + "id": 14, + "origin": 3, + "destination": 2, + "tick": 29 + }, + { + "id": 15, + "origin": 4, + "destination": 3, + "tick": 30 + }, + { + "id": 16, + "origin": 4, + "destination": 1, + "tick": 32 + }, + { + "id": 17, + "origin": 0, + "destination": 1, + "tick": 35 + }, + { + "id": 18, + "origin": 2, + "destination": 0, + "tick": 38 + }, + { + "id": 19, + "origin": 5, + "destination": 3, + "tick": 50 + }, + { + "id": 20, + "origin": 5, + "destination": 2, + "tick": 54 + }, + { + "id": 21, + "origin": 4, + "destination": 3, + "tick": 56 + }, + { + "id": 22, + "origin": 1, + "destination": 5, + "tick": 57 + }, + { + "id": 23, + "origin": 4, + "destination": 1, + "tick": 58 + }, + { + "id": 24, + "origin": 5, + "destination": 1, + "tick": 60 + }, + { + "id": 25, + "origin": 4, + "destination": 5, + "tick": 62 + }, + { + "id": 26, + "origin": 5, + "destination": 0, + "tick": 63 + }, + { + "id": 27, + "origin": 0, + "destination": 2, + "tick": 64 + }, + { + "id": 28, + "origin": 4, + "destination": 1, + "tick": 65 + }, + { + "id": 29, + "origin": 2, + "destination": 1, + "tick": 66 + }, + { + "id": 30, + "origin": 2, + "destination": 4, + "tick": 67 + }, + { + "id": 31, + "origin": 3, + "destination": 5, + "tick": 69 + }, + { + "id": 32, + "origin": 2, + "destination": 5, + "tick": 71 + }, + { + "id": 33, + "origin": 2, + "destination": 0, + "tick": 72 + }, + { + "id": 34, + "origin": 4, + "destination": 1, + "tick": 73 + }, + { + "id": 35, + "origin": 3, + "destination": 0, + "tick": 74 + }, + { + "id": 36, + "origin": 0, + "destination": 3, + "tick": 76 + }, + { + "id": 37, + "origin": 0, + "destination": 5, + "tick": 77 + }, + { + "id": 38, + "origin": 3, + "destination": 2, + "tick": 78 + }, + { + "id": 39, + "origin": 3, + "destination": 1, + "tick": 79 + }, + { + "id": 40, + "origin": 3, + "destination": 1, + "tick": 87 + }, + { + "id": 41, + "origin": 3, + "destination": 2, + "tick": 91 + }, + { + "id": 42, + "origin": 1, + "destination": 3, + "tick": 95 + }, + { + "id": 43, + "origin": 4, + "destination": 3, + "tick": 96 + }, + { + "id": 44, + "origin": 3, + "destination": 0, + "tick": 97 + }, + { + "id": 45, + "origin": 1, + "destination": 4, + "tick": 98 + }, + { + "id": 46, + "origin": 1, + "destination": 5, + "tick": 102 + }, + { + "id": 47, + "origin": 5, + "destination": 0, + "tick": 103 + }, + { + "id": 48, + "origin": 2, + "destination": 5, + "tick": 105 + }, + { + "id": 49, + "origin": 5, + "destination": 3, + "tick": 106 + }, + { + "id": 50, + "origin": 0, + "destination": 3, + "tick": 109 + }, + { + "id": 51, + "origin": 1, + "destination": 5, + "tick": 110 + }, + { + "id": 52, + "origin": 0, + "destination": 5, + "tick": 111 + }, + { + "id": 53, + "origin": 3, + "destination": 2, + "tick": 112 + }, + { + "id": 54, + "origin": 5, + "destination": 1, + "tick": 114 + }, + { + "id": 55, + "origin": 1, + "destination": 4, + "tick": 116 + }, + { + "id": 56, + "origin": 1, + "destination": 5, + "tick": 117 + }, + { + "id": 57, + "origin": 1, + "destination": 4, + "tick": 118 + }, + { + "id": 58, + "origin": 1, + "destination": 4, + "tick": 119 + }, + { + "id": 59, + "origin": 0, + "destination": 2, + "tick": 121 + }, + { + "id": 60, + "origin": 0, + "destination": 4, + "tick": 122 + }, + { + "id": 61, + "origin": 4, + "destination": 2, + "tick": 123 + }, + { + "id": 62, + "origin": 0, + "destination": 3, + "tick": 124 + }, + { + "id": 63, + "origin": 4, + "destination": 3, + "tick": 128 + }, + { + "id": 64, + "origin": 1, + "destination": 5, + "tick": 129 + }, + { + "id": 65, + "origin": 5, + "destination": 2, + "tick": 133 + }, + { + "id": 66, + "origin": 1, + "destination": 4, + "tick": 134 + }, + { + "id": 67, + "origin": 5, + "destination": 3, + "tick": 136 + }, + { + "id": 68, + "origin": 1, + "destination": 0, + "tick": 145 + }, + { + "id": 69, + "origin": 3, + "destination": 2, + "tick": 146 + }, + { + "id": 70, + "origin": 3, + "destination": 5, + "tick": 148 + }, + { + "id": 71, + "origin": 4, + "destination": 5, + "tick": 149 + }, + { + "id": 72, + "origin": 2, + "destination": 5, + "tick": 151 + }, + { + "id": 73, + "origin": 4, + "destination": 3, + "tick": 155 + }, + { + "id": 74, + "origin": 5, + "destination": 3, + "tick": 157 + }, + { + "id": 75, + "origin": 5, + "destination": 0, + "tick": 158 + }, + { + "id": 76, + "origin": 2, + "destination": 0, + "tick": 162 + }, + { + "id": 77, + "origin": 4, + "destination": 2, + "tick": 163 + }, + { + "id": 78, + "origin": 5, + "destination": 3, + "tick": 165 + }, + { + "id": 79, + "origin": 2, + "destination": 0, + "tick": 166 + }, + { + "id": 80, + "origin": 2, + "destination": 4, + "tick": 168 + }, + { + "id": 81, + "origin": 3, + "destination": 1, + "tick": 169 + }, + { + "id": 82, + "origin": 0, + "destination": 5, + "tick": 173 + }, + { + "id": 83, + "origin": 4, + "destination": 3, + "tick": 174 + }, + { + "id": 84, + "origin": 5, + "destination": 4, + "tick": 175 + }, + { + "id": 85, + "origin": 3, + "destination": 4, + "tick": 177 + }, + { + "id": 86, + "origin": 0, + "destination": 3, + "tick": 180 + }, + { + "id": 87, + "origin": 2, + "destination": 5, + "tick": 181 + }, + { + "id": 88, + "origin": 1, + "destination": 4, + "tick": 185 + }, + { + "id": 89, + "origin": 5, + "destination": 3, + "tick": 191 + }, + { + "id": 90, + "origin": 0, + "destination": 1, + "tick": 192 + }, + { + "id": 91, + "origin": 5, + "destination": 2, + "tick": 193 + }, + { + "id": 92, + "origin": 3, + "destination": 4, + "tick": 194 + }, + { + "id": 93, + "origin": 4, + "destination": 2, + "tick": 195 + }, + { + "id": 94, + "origin": 2, + "destination": 4, + "tick": 196 + }, + { + "id": 95, + "origin": 3, + "destination": 2, + "tick": 197 + }, + { + "id": 96, + "origin": 5, + "destination": 3, + "tick": 199 + } + ] +} \ No newline at end of file diff --git a/elevator_saga/traffic/random.json b/elevator_saga/traffic/random.json new file mode 100644 index 0000000..9f37078 --- /dev/null +++ b/elevator_saga/traffic/random.json @@ -0,0 +1,482 @@ +{ + "building": { + "floors": 6, + "elevators": 2, + "elevator_capacity": 8, + "scenario": "random", + "scale": "medium", + "description": "随机流量 - 均匀分布,适合所有规模 (medium规模)", + "expected_passengers": 78, + "duration": 200 + }, + "traffic": [ + { + "id": 1, + "origin": 1, + "destination": 2, + "tick": 3 + }, + { + "id": 2, + "origin": 4, + "destination": 2, + "tick": 5 + }, + { + "id": 3, + "origin": 5, + "destination": 4, + "tick": 10 + }, + { + "id": 4, + "origin": 0, + "destination": 3, + "tick": 15 + }, + { + "id": 5, + "origin": 2, + "destination": 4, + "tick": 18 + }, + { + "id": 6, + "origin": 4, + "destination": 5, + "tick": 19 + }, + { + "id": 7, + "origin": 4, + "destination": 2, + "tick": 20 + }, + { + "id": 8, + "origin": 3, + "destination": 4, + "tick": 25 + }, + { + "id": 9, + "origin": 0, + "destination": 2, + "tick": 26 + }, + { + "id": 10, + "origin": 0, + "destination": 4, + "tick": 28 + }, + { + "id": 11, + "origin": 3, + "destination": 4, + "tick": 36 + }, + { + "id": 12, + "origin": 4, + "destination": 3, + "tick": 38 + }, + { + "id": 13, + "origin": 0, + "destination": 4, + "tick": 39 + }, + { + "id": 14, + "origin": 5, + "destination": 2, + "tick": 40 + }, + { + "id": 15, + "origin": 1, + "destination": 3, + "tick": 41 + }, + { + "id": 16, + "origin": 1, + "destination": 5, + "tick": 42 + }, + { + "id": 17, + "origin": 1, + "destination": 2, + "tick": 46 + }, + { + "id": 18, + "origin": 0, + "destination": 4, + "tick": 48 + }, + { + "id": 19, + "origin": 2, + "destination": 5, + "tick": 51 + }, + { + "id": 20, + "origin": 0, + "destination": 5, + "tick": 53 + }, + { + "id": 21, + "origin": 3, + "destination": 4, + "tick": 54 + }, + { + "id": 22, + "origin": 4, + "destination": 3, + "tick": 55 + }, + { + "id": 23, + "origin": 4, + "destination": 3, + "tick": 61 + }, + { + "id": 24, + "origin": 0, + "destination": 1, + "tick": 62 + }, + { + "id": 25, + "origin": 0, + "destination": 4, + "tick": 63 + }, + { + "id": 26, + "origin": 1, + "destination": 0, + "tick": 70 + }, + { + "id": 27, + "origin": 0, + "destination": 3, + "tick": 72 + }, + { + "id": 28, + "origin": 1, + "destination": 2, + "tick": 73 + }, + { + "id": 29, + "origin": 1, + "destination": 4, + "tick": 74 + }, + { + "id": 30, + "origin": 5, + "destination": 2, + "tick": 77 + }, + { + "id": 31, + "origin": 5, + "destination": 4, + "tick": 81 + }, + { + "id": 32, + "origin": 1, + "destination": 3, + "tick": 83 + }, + { + "id": 33, + "origin": 5, + "destination": 4, + "tick": 86 + }, + { + "id": 34, + "origin": 0, + "destination": 2, + "tick": 88 + }, + { + "id": 35, + "origin": 3, + "destination": 2, + "tick": 89 + }, + { + "id": 36, + "origin": 1, + "destination": 0, + "tick": 93 + }, + { + "id": 37, + "origin": 5, + "destination": 0, + "tick": 98 + }, + { + "id": 38, + "origin": 4, + "destination": 3, + "tick": 104 + }, + { + "id": 39, + "origin": 2, + "destination": 4, + "tick": 106 + }, + { + "id": 40, + "origin": 2, + "destination": 1, + "tick": 110 + }, + { + "id": 41, + "origin": 4, + "destination": 0, + "tick": 117 + }, + { + "id": 42, + "origin": 3, + "destination": 5, + "tick": 119 + }, + { + "id": 43, + "origin": 0, + "destination": 1, + "tick": 121 + }, + { + "id": 44, + "origin": 3, + "destination": 4, + "tick": 122 + }, + { + "id": 45, + "origin": 2, + "destination": 3, + "tick": 123 + }, + { + "id": 46, + "origin": 1, + "destination": 0, + "tick": 124 + }, + { + "id": 47, + "origin": 3, + "destination": 5, + "tick": 125 + }, + { + "id": 48, + "origin": 1, + "destination": 0, + "tick": 127 + }, + { + "id": 49, + "origin": 0, + "destination": 2, + "tick": 129 + }, + { + "id": 50, + "origin": 5, + "destination": 0, + "tick": 133 + }, + { + "id": 51, + "origin": 2, + "destination": 1, + "tick": 135 + }, + { + "id": 52, + "origin": 1, + "destination": 0, + "tick": 137 + }, + { + "id": 53, + "origin": 4, + "destination": 2, + "tick": 138 + }, + { + "id": 54, + "origin": 1, + "destination": 0, + "tick": 141 + }, + { + "id": 55, + "origin": 1, + "destination": 3, + "tick": 143 + }, + { + "id": 56, + "origin": 0, + "destination": 1, + "tick": 145 + }, + { + "id": 57, + "origin": 2, + "destination": 4, + "tick": 147 + }, + { + "id": 58, + "origin": 2, + "destination": 5, + "tick": 149 + }, + { + "id": 59, + "origin": 2, + "destination": 4, + "tick": 151 + }, + { + "id": 60, + "origin": 5, + "destination": 4, + "tick": 152 + }, + { + "id": 61, + "origin": 4, + "destination": 3, + "tick": 156 + }, + { + "id": 62, + "origin": 3, + "destination": 1, + "tick": 158 + }, + { + "id": 63, + "origin": 4, + "destination": 0, + "tick": 160 + }, + { + "id": 64, + "origin": 4, + "destination": 2, + "tick": 161 + }, + { + "id": 65, + "origin": 5, + "destination": 0, + "tick": 164 + }, + { + "id": 66, + "origin": 0, + "destination": 1, + "tick": 167 + }, + { + "id": 67, + "origin": 3, + "destination": 2, + "tick": 168 + }, + { + "id": 68, + "origin": 1, + "destination": 5, + "tick": 170 + }, + { + "id": 69, + "origin": 4, + "destination": 1, + "tick": 171 + }, + { + "id": 70, + "origin": 5, + "destination": 1, + "tick": 173 + }, + { + "id": 71, + "origin": 0, + "destination": 4, + "tick": 177 + }, + { + "id": 72, + "origin": 2, + "destination": 4, + "tick": 180 + }, + { + "id": 73, + "origin": 2, + "destination": 0, + "tick": 186 + }, + { + "id": 74, + "origin": 1, + "destination": 0, + "tick": 188 + }, + { + "id": 75, + "origin": 0, + "destination": 2, + "tick": 190 + }, + { + "id": 76, + "origin": 1, + "destination": 4, + "tick": 194 + }, + { + "id": 77, + "origin": 0, + "destination": 2, + "tick": 195 + }, + { + "id": 78, + "origin": 3, + "destination": 4, + "tick": 196 + } + ] +} \ No newline at end of file diff --git a/elevator_saga/traffic/up_peak.json b/elevator_saga/traffic/up_peak.json new file mode 100644 index 0000000..2daf779 --- /dev/null +++ b/elevator_saga/traffic/up_peak.json @@ -0,0 +1,494 @@ +{ + "building": { + "floors": 6, + "elevators": 2, + "elevator_capacity": 8, + "scenario": "up_peak", + "scale": "medium", + "description": "上行高峰 - 主要从底层到高层 (medium规模)", + "expected_passengers": 80, + "duration": 200 + }, + "traffic": [ + { + "id": 1, + "origin": 0, + "destination": 5, + "tick": 1 + }, + { + "id": 2, + "origin": 4, + "destination": 5, + "tick": 2 + }, + { + "id": 3, + "origin": 0, + "destination": 4, + "tick": 4 + }, + { + "id": 4, + "origin": 0, + "destination": 3, + "tick": 6 + }, + { + "id": 5, + "origin": 0, + "destination": 2, + "tick": 9 + }, + { + "id": 6, + "origin": 0, + "destination": 3, + "tick": 11 + }, + { + "id": 7, + "origin": 0, + "destination": 4, + "tick": 20 + }, + { + "id": 8, + "origin": 0, + "destination": 3, + "tick": 21 + }, + { + "id": 9, + "origin": 0, + "destination": 4, + "tick": 22 + }, + { + "id": 10, + "origin": 0, + "destination": 4, + "tick": 24 + }, + { + "id": 11, + "origin": 0, + "destination": 1, + "tick": 25 + }, + { + "id": 12, + "origin": 4, + "destination": 5, + "tick": 26 + }, + { + "id": 13, + "origin": 0, + "destination": 2, + "tick": 27 + }, + { + "id": 14, + "origin": 0, + "destination": 2, + "tick": 28 + }, + { + "id": 15, + "origin": 4, + "destination": 5, + "tick": 30 + }, + { + "id": 16, + "origin": 0, + "destination": 1, + "tick": 31 + }, + { + "id": 17, + "origin": 0, + "destination": 3, + "tick": 33 + }, + { + "id": 18, + "origin": 0, + "destination": 3, + "tick": 37 + }, + { + "id": 19, + "origin": 0, + "destination": 2, + "tick": 38 + }, + { + "id": 20, + "origin": 0, + "destination": 4, + "tick": 42 + }, + { + "id": 21, + "origin": 0, + "destination": 1, + "tick": 46 + }, + { + "id": 22, + "origin": 0, + "destination": 3, + "tick": 47 + }, + { + "id": 23, + "origin": 0, + "destination": 1, + "tick": 51 + }, + { + "id": 24, + "origin": 1, + "destination": 2, + "tick": 54 + }, + { + "id": 25, + "origin": 0, + "destination": 1, + "tick": 55 + }, + { + "id": 26, + "origin": 0, + "destination": 5, + "tick": 56 + }, + { + "id": 27, + "origin": 0, + "destination": 3, + "tick": 61 + }, + { + "id": 28, + "origin": 0, + "destination": 4, + "tick": 64 + }, + { + "id": 29, + "origin": 0, + "destination": 4, + "tick": 65 + }, + { + "id": 30, + "origin": 0, + "destination": 1, + "tick": 66 + }, + { + "id": 31, + "origin": 0, + "destination": 4, + "tick": 67 + }, + { + "id": 32, + "origin": 0, + "destination": 3, + "tick": 68 + }, + { + "id": 33, + "origin": 0, + "destination": 5, + "tick": 71 + }, + { + "id": 34, + "origin": 0, + "destination": 1, + "tick": 72 + }, + { + "id": 35, + "origin": 0, + "destination": 1, + "tick": 73 + }, + { + "id": 36, + "origin": 0, + "destination": 5, + "tick": 75 + }, + { + "id": 37, + "origin": 0, + "destination": 2, + "tick": 79 + }, + { + "id": 38, + "origin": 0, + "destination": 4, + "tick": 80 + }, + { + "id": 39, + "origin": 0, + "destination": 4, + "tick": 82 + }, + { + "id": 40, + "origin": 0, + "destination": 4, + "tick": 84 + }, + { + "id": 41, + "origin": 0, + "destination": 5, + "tick": 85 + }, + { + "id": 42, + "origin": 0, + "destination": 5, + "tick": 86 + }, + { + "id": 43, + "origin": 0, + "destination": 5, + "tick": 87 + }, + { + "id": 44, + "origin": 0, + "destination": 1, + "tick": 90 + }, + { + "id": 45, + "origin": 0, + "destination": 5, + "tick": 91 + }, + { + "id": 46, + "origin": 4, + "destination": 5, + "tick": 92 + }, + { + "id": 47, + "origin": 0, + "destination": 3, + "tick": 95 + }, + { + "id": 48, + "origin": 0, + "destination": 2, + "tick": 96 + }, + { + "id": 49, + "origin": 0, + "destination": 1, + "tick": 97 + }, + { + "id": 50, + "origin": 0, + "destination": 5, + "tick": 99 + }, + { + "id": 51, + "origin": 0, + "destination": 1, + "tick": 100 + }, + { + "id": 52, + "origin": 0, + "destination": 2, + "tick": 101 + }, + { + "id": 53, + "origin": 2, + "destination": 5, + "tick": 103 + }, + { + "id": 54, + "origin": 0, + "destination": 5, + "tick": 104 + }, + { + "id": 55, + "origin": 0, + "destination": 2, + "tick": 108 + }, + { + "id": 56, + "origin": 0, + "destination": 4, + "tick": 109 + }, + { + "id": 57, + "origin": 0, + "destination": 5, + "tick": 110 + }, + { + "id": 58, + "origin": 0, + "destination": 2, + "tick": 111 + }, + { + "id": 59, + "origin": 0, + "destination": 4, + "tick": 113 + }, + { + "id": 60, + "origin": 0, + "destination": 3, + "tick": 115 + }, + { + "id": 61, + "origin": 0, + "destination": 1, + "tick": 117 + }, + { + "id": 62, + "origin": 0, + "destination": 1, + "tick": 118 + }, + { + "id": 63, + "origin": 0, + "destination": 4, + "tick": 119 + }, + { + "id": 64, + "origin": 0, + "destination": 5, + "tick": 120 + }, + { + "id": 65, + "origin": 0, + "destination": 3, + "tick": 121 + }, + { + "id": 66, + "origin": 0, + "destination": 3, + "tick": 123 + }, + { + "id": 67, + "origin": 0, + "destination": 5, + "tick": 124 + }, + { + "id": 68, + "origin": 0, + "destination": 1, + "tick": 127 + }, + { + "id": 69, + "origin": 4, + "destination": 5, + "tick": 128 + }, + { + "id": 70, + "origin": 0, + "destination": 1, + "tick": 129 + }, + { + "id": 71, + "origin": 0, + "destination": 4, + "tick": 130 + }, + { + "id": 72, + "origin": 0, + "destination": 5, + "tick": 131 + }, + { + "id": 73, + "origin": 0, + "destination": 3, + "tick": 133 + }, + { + "id": 74, + "origin": 0, + "destination": 2, + "tick": 134 + }, + { + "id": 75, + "origin": 0, + "destination": 1, + "tick": 137 + }, + { + "id": 76, + "origin": 0, + "destination": 4, + "tick": 141 + }, + { + "id": 77, + "origin": 0, + "destination": 2, + "tick": 142 + }, + { + "id": 78, + "origin": 0, + "destination": 1, + "tick": 144 + }, + { + "id": 79, + "origin": 0, + "destination": 2, + "tick": 145 + }, + { + "id": 80, + "origin": 0, + "destination": 4, + "tick": 146 + } + ] +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e765fd7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,154 @@ +[build-system] +requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"] +build-backend = "setuptools.build_meta" + +[project] +name = "elevator-saga" +dynamic = ["version"] +description = "Python implementation of Elevator Saga game with PyEE event system" +readme = "README_CN.md" +license = {text = "MIT"} +authors = [ + {name = "Elevator Saga Team"}, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", + "Topic :: Games/Entertainment :: Simulation", + "Topic :: Software Development :: Libraries :: Python Modules", +] +requires-python = ">=3.12" +dependencies = [ + "pyee>=11.0.0", + "numpy>=1.20.0", + "matplotlib>=3.5.0", + "seaborn>=0.11.0", + "pandas>=1.3.0", + "flask", +] + +[project.optional-dependencies] +dev = [ + "pytest>=6.0", + "pytest-cov", + "black", + "flake8", + "isort", + "mypy", +] + +[project.scripts] +elevator-saga = "elevator_saga.cli.main:main" +elevator-server = "elevator_saga.cli.main:server_main" +elevator-client = "elevator_saga.cli.main:client_main" +elevator-grader = "elevator_saga.grader.grader:main" +elevator-batch-test = "elevator_saga.grader.batch_runner:main" + +[project.urls] +Repository = "https://github.com/yourusername/elevator-saga" +Issues = "https://github.com/yourusername/elevator-saga/issues" + +[tool.setuptools_scm] +write_to = "elevator_saga/_version.py" + +[tool.setuptools.packages.find] +where = ["."] +include = ["elevator_saga*"] + +[tool.black] +line-length = 120 +target-version = ['py312'] +include = '\.pyi?$' +extend-exclude = ''' +/( + # directories + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' + +[tool.isort] +profile = "black" +line_length = 120 +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true + +[tool.mypy] +python_version = "3.12" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true +disallow_incomplete_defs = true +check_untyped_defs = true +disallow_untyped_decorators = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_no_return = true +warn_unreachable = true +strict_equality = true + +[[tool.mypy.overrides]] +module = [ + "pyee.*", + "matplotlib.*", + "seaborn.*", + "pandas.*", + "numpy.*", + "flask.*", +] +ignore_missing_imports = true + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py", "*_test.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +addopts = [ + "--strict-markers", + "--strict-config", + "--verbose", +] +markers = [ + "slow: marks tests as slow (deselect with '-m \"not slow\"')", + "integration: marks tests as integration tests", +] + +[tool.coverage.run] +source = ["elevator_saga"] +omit = [ + "*/tests/*", + "*/test_*", + "*/__pycache__/*", + "*/.*", +] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "if self.debug:", + "if settings.DEBUG", + "raise AssertionError", + "raise NotImplementedError", + "if 0:", + "if __name__ == .__main__.:", + "class .*\\bProtocol\\):", + "@(abc\\.)?abstractmethod", +] diff --git a/run_all_tests.py b/run_all_tests.py new file mode 100644 index 0000000..bd891ee --- /dev/null +++ b/run_all_tests.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +""" +Test runner for Elevator Saga project +""" +import argparse +import subprocess +import sys +import os +from pathlib import Path + + +def check_dependencies(): + """Check if all dependencies are installed correctly""" + print("🔍 Checking dependencies...") + try: + import elevator_saga + + print(f"✅ elevator_saga version: {getattr(elevator_saga, '__version__', 'unknown')}") + + # Check main dependencies + dependencies = ["pyee", "numpy", "matplotlib", "seaborn", "pandas", "flask"] + for dep in dependencies: + try: + __import__(dep) + print(f"✅ {dep}: installed") + except ImportError: + print(f"❌ {dep}: missing") + return False + + print("✅ All dependencies are correctly installed") + return True + except ImportError as e: + print(f"❌ Error importing elevator_saga: {e}") + return False + + +def run_unit_tests(): + """Run unit tests""" + print("🧪 Running unit tests...") + + # Check if tests directory exists + tests_dir = Path("tests") + if not tests_dir.exists(): + print("ℹ️ No tests directory found, creating basic test structure...") + tests_dir.mkdir() + (tests_dir / "__init__.py").touch() + + # Create a basic test file + basic_test = tests_dir / "test_basic.py" + basic_test.write_text( + '''"""Basic tests for elevator_saga""" +import unittest +from elevator_saga.core.models import Direction, SimulationEvent + + +class TestBasic(unittest.TestCase): + """Basic functionality tests""" + + def test_direction_enum(self): + """Test Direction enum""" + self.assertEqual(Direction.UP.value, "up") + self.assertEqual(Direction.DOWN.value, "down") + self.assertEqual(Direction.NONE.value, "none") + + def test_import(self): + """Test that main modules can be imported""" + import elevator_saga.client.base_controller + import elevator_saga.core.models + import elevator_saga.server.simulator + + +if __name__ == '__main__': + unittest.main() +''' + ) + + # Run pytest if available, otherwise unittest + try: + result = subprocess.run([sys.executable, "-m", "pytest", "tests/", "-v"], capture_output=True, text=True) + print(result.stdout) + if result.stderr: + print(result.stderr) + return result.returncode == 0 + except FileNotFoundError: + print("pytest not found, using unittest...") + result = subprocess.run([sys.executable, "-m", "unittest", "discover", "tests"], capture_output=True, text=True) + print(result.stdout) + if result.stderr: + print(result.stderr) + return result.returncode == 0 + + +def run_example_tests(): + """Run example files to ensure they work""" + print("🚀 Running example tests...") + + example_files = ["simple_example.py", "test_example.py"] + for example_file in example_files: + if os.path.exists(example_file): + print(f"Testing {example_file}...") + # Just check if the file can be imported without errors + try: + result = subprocess.run( + [sys.executable, "-c", f"import {example_file[:-3]}"], capture_output=True, text=True, timeout=10 + ) + if result.returncode == 0: + print(f"✅ {example_file}: import successful") + else: + print(f"❌ {example_file}: import failed") + print(result.stderr) + return False + except subprocess.TimeoutExpired: + print(f"⏰ {example_file}: timeout (probably waiting for server)") + # This is expected for examples that try to connect to server + print(f"✅ {example_file}: import successful (with server connection)") + except Exception as e: + print(f"❌ {example_file}: error - {e}") + return False + + return True + + +def main(): + parser = argparse.ArgumentParser(description="Run tests for Elevator Saga") + parser.add_argument("--check-deps", action="store_true", help="Check dependencies only") + parser.add_argument("--type", choices=["unit", "examples", "all"], default="all", help="Type of tests to run") + + args = parser.parse_args() + + success = True + + if args.check_deps: + success = check_dependencies() + else: + # Always check dependencies first + if not check_dependencies(): + print("❌ Dependency check failed") + return 1 + + if args.type in ["unit", "all"]: + if not run_unit_tests(): + success = False + + if args.type in ["examples", "all"]: + if not run_example_tests(): + success = False + + if success: + print("🎉 All tests passed!") + return 0 + else: + print("💥 Some tests failed!") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/setup.py b/setup.py index afb503c..c91b433 100644 --- a/setup.py +++ b/setup.py @@ -18,26 +18,22 @@ setup( classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", - "Intended Audience :: Education", + "Intended Audience :: Education", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Games/Entertainment :: Simulation", "Topic :: Software Development :: Libraries :: Python Modules", ], - python_requires=">=3.8", + python_requires=">=3.12", install_requires=[ "pyee>=11.0.0", "numpy>=1.20.0", "matplotlib>=3.5.0", "seaborn>=0.11.0", "pandas>=1.3.0", - "flask" + "flask", ], extras_require={ "dev": [ @@ -45,6 +41,8 @@ setup( "pytest-cov", "black", "flake8", + "isort", + "mypy", ], }, entry_points={ diff --git a/test_example.py b/test_example.py index f384776..fd5781a 100644 --- a/test_example.py +++ b/test_example.py @@ -6,17 +6,29 @@ from elevator_saga.client.proxy_models import ProxyElevator, ProxyFloor, ProxyPa from elevator_saga.core.models import SimulationEvent, Direction -class TestElevatorBusController(ElevatorController): +class SingleElevatorBusController(ElevatorController): def __init__(self): super().__init__("http://127.0.0.1:8000", True) + self.all_passengers: List[ProxyPassenger] = [] + self.max_floor = 0 def on_init(self, elevators: List[ProxyElevator], floors: List[ProxyFloor]) -> None: + self.max_floor = floors[-1].floor + self.floors = floors + for i, elevator in enumerate(elevators): + # 计算目标楼层 - 均匀分布在不同楼层 + target_floor = (i * (len(floors) - 1)) // len(elevators) + # 立刻移动到目标位置并开始循环 + elevator.go_to_floor(target_floor, immediate=True) pass def on_event_execute_start( self, tick: int, events: List[SimulationEvent], elevators: List[ProxyElevator], floors: List[ProxyFloor] ) -> None: - pass + print(f"Tick {tick}: 即将处理 {len(events)} 个事件 {[e.type.value for e in events]}") + for i in elevators: + print(f"\t{i.id}[{i.target_floor_direction.value},{i.current_floor_float}/{i.target_floor}]" + "👦" * len(i.passengers), end="") + print() def on_event_execute_end( self, tick: int, events: List[SimulationEvent], elevators: List[ProxyElevator], floors: List[ProxyFloor] @@ -24,13 +36,24 @@ class TestElevatorBusController(ElevatorController): pass def on_passenger_call(self, passenger:ProxyPassenger, floor: ProxyFloor, direction: str) -> None: + self.all_passengers.append(passenger) pass def on_elevator_idle(self, elevator: ProxyElevator) -> None: - pass + elevator.go_to_floor(1) def on_elevator_stopped(self, elevator: ProxyElevator, floor: ProxyFloor) -> None: - pass + print(f"🛑 电梯 E{elevator.id} 停靠在 F{floor.floor}") + # BUS调度算法,电梯到达顶层后,立即下降一层 + if elevator.last_tick_direction == Direction.UP and elevator.current_floor == self.max_floor: + elevator.go_to_floor(elevator.current_floor - 1) + # 电梯到达底层后,立即上升一层 + elif elevator.last_tick_direction == Direction.DOWN and elevator.current_floor == 0: + elevator.go_to_floor(elevator.current_floor + 1) + elif elevator.last_tick_direction == Direction.UP: + elevator.go_to_floor(elevator.current_floor + 1) + elif elevator.last_tick_direction == Direction.DOWN: + elevator.go_to_floor(elevator.current_floor - 1) def on_passenger_board(self, elevator: ProxyElevator, passenger: ProxyPassenger) -> None: pass @@ -45,5 +68,5 @@ class TestElevatorBusController(ElevatorController): pass if __name__ == "__main__": - algorithm = TestElevatorBusController() + algorithm = SingleElevatorBusController() algorithm.start()