一、项目概述
本应用旨在通过WPF实现电梯调度算法的动态可视化演示,帮助用户直观理解不同电梯调度算法的工作原理和性能差异。
二、项目结构
ElevatorSimulation/
├── ElevatorSimulation/ # 主程序
│ ├── Models/ # 数据模型
│ ├── Views/ # 视图层
│ ├── ViewModels/ # 视图模型
│ ├── Algorithms/ # 算法实现
│ └── Resources/ # 资源文件
└── ElevatorSimulation.sln # 解决方案文件
三、核心功能实现
1. 数据模型 (Models/ElevatorModel.cs)
public class ElevatorModel
{
public int CurrentFloor { get; set; } = 1;
public Direction CurrentDirection { get; set; } = Direction.Idle;
public List<int> RequestedFloors { get; } = new List<int>();
public bool IsMoving { get; set; } = false;
public enum Direction
{
Up,
Down,
Idle
}
}
public class FloorRequest
{
public int FloorNumber { get; set; }
public DateTime RequestTime { get; set; }
public bool IsCompleted { get; set; } = false;
}
2. 视图模型 (ViewModels/MainViewModel.cs)
public class MainViewModel : INotifyPropertyChanged
{
private ElevatorModel _elevator;
private List<FloorRequest> _requests;
private ElevatorAlgorithm _currentAlgorithm;
public event PropertyChangedEventHandler PropertyChanged;
public ElevatorModel Elevator
{
get => _elevator;
set
{
_elevator = value;
OnPropertyChanged();
}
}
public List<FloorRequest> Requests
{
get => _requests;
set
{
_requests = value;
OnPropertyChanged();
}
}
public ElevatorAlgorithm CurrentAlgorithm
{
get => _currentAlgorithm;
set
{
_currentAlgorithm = value;
OnPropertyChanged();
}
}
public ICommand StartSimulationCommand { get; }
public ICommand AddRequestCommand { get; }
public ICommand ResetSimulationCommand { get; }
public MainViewModel()
{
Elevator = new ElevatorModel();
Requests = new List<FloorRequest>();
CurrentAlgorithm = ElevatorAlgorithm.FCFS;
StartSimulationCommand = new RelayCommand(StartSimulation);
AddRequestCommand = new RelayCommand(AddRequest);
ResetSimulationCommand = new RelayCommand(ResetSimulation);
}
private void StartSimulation()
{
// 启动电梯模拟线程
Task.Run(() => SimulateElevator());
}
private void SimulateElevator()
{
while (Requests.Any(r => !r.IsCompleted))
{
// 根据当前算法选择下一个目标楼层
int nextFloor = CurrentAlgorithm.SelectNextFloor(Elevator, Requests);
if (nextFloor != Elevator.CurrentFloor)
{
Elevator.IsMoving = true;
// 移动电梯
if (nextFloor > Elevator.CurrentFloor)
{
Elevator.CurrentDirection = ElevatorModel.Direction.Up;
for (int i = Elevator.CurrentFloor + 1; i <= nextFloor; i++)
{
Elevator.CurrentFloor = i;
UpdateRequests();
Thread.Sleep(500); // 模拟移动时间
}
}
else if (nextFloor < Elevator.CurrentFloor)
{
Elevator.CurrentDirection = ElevatorModel.Direction.Down;
for (int i = Elevator.CurrentFloor - 1; i >= nextFloor; i--)
{
Elevator.CurrentFloor = i;
UpdateRequests();
Thread.Sleep(500);
}
}
Elevator.IsMoving = false;
}
// 处理到达楼层的请求
ProcessRequestsAtCurrentFloor();
}
Elevator.CurrentDirection = ElevatorModel.Direction.Idle;
}
private void ProcessRequestsAtCurrentFloor()
{
var requestsAtFloor = Requests.Where(r => r.FloorNumber == Elevator.CurrentFloor && !r.IsCompleted).ToList();
foreach (var request in requestsAtFloor)
{
request.IsCompleted = true;
// 这里可以添加乘客进出电梯的逻辑
}
}
private void UpdateRequests()
{
// 更新UI中的请求状态
// 可以通过事件或INotifyPropertyChanged通知视图更新
}
private void AddRequest()
{
// 添加新的楼层请求
var newRequest = new FloorRequest
{
FloorNumber = new Random().Next(1, 21), // 假设20层楼
RequestTime = DateTime.Now
};
Requests.Add(newRequest);
}
private void ResetSimulation()
{
Elevator.CurrentFloor = 1;
Elevator.CurrentDirection = ElevatorModel.Direction.Idle;
Elevator.IsMoving = false;
Requests.ForEach(r => r.IsCompleted = false);
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
3. 算法实现 (Algorithms/ElevatorAlgorithm.cs)
public enum ElevatorAlgorithm
{
FCFS, // 先来先服务
SSTF, // 最短寻道时间优先
SCAN, // 电梯调度算法
C_SCAN // 循环电梯调度算法
}
public static class ElevatorAlgorithmExtensions
{
public static int SelectNextFloor(this ElevatorAlgorithm algorithm, ElevatorModel elevator, List<FloorRequest> requests)
{
switch (algorithm)
{
case ElevatorAlgorithm.FCFS:
return FCFS(elevator, requests);
case ElevatorAlgorithm.SSTF:
return SSTF(elevator, requests);
case ElevatorAlgorithm.SCAN:
return SCAN(elevator, requests);
case ElevatorAlgorithm.C_SCAN:
return C_SCAN(elevator, requests);
default:
return elevator.CurrentFloor;
}
}
private static int FCFS(ElevatorModel elevator, List<FloorRequest> requests)
{
var pendingRequests = requests.Where(r => !r.IsCompleted).ToList();
if (!pendingRequests.Any())
return elevator.CurrentFloor;
// 按请求时间排序
var sortedRequests = pendingRequests.OrderBy(r => r.RequestTime).ToList();
// 根据当前方向选择下一个请求
if (elevator.CurrentDirection == ElevatorModel.Direction.Up)
{
var upRequests = sortedRequests.Where(r => r.FloorNumber >= elevator.CurrentFloor).ToList();
if (upRequests.Any())
return upRequests.First().FloorNumber;
else
return sortedRequests.Last().FloorNumber; // 没有上行请求,返回最下面的请求
}
else if (elevator.CurrentDirection == ElevatorModel.Direction.Down)
{
var downRequests = sortedRequests.Where(r => r.FloorNumber <= elevator.CurrentFloor).Reverse().ToList();
if (downRequests.Any())
return downRequests.First().FloorNumber;
else
return sortedRequests.First().FloorNumber; // 没有下行请求,返回最上面的请求
}
// 默认返回第一个请求
return sortedRequests.First().FloorNumber;
}
private static int SSTF(ElevatorModel elevator, List<FloorRequest> requests)
{
var pendingRequests = requests.Where(r => !r.IsCompleted).ToList();
if (!pendingRequests.Any())
return elevator.CurrentFloor;
// 计算每个请求与当前楼层的距离
var distances = pendingRequests.Select(r => new
{
Request = r,
Distance = Math.Abs(r.FloorNumber - elevator.CurrentFloor)
}).ToList();
// 根据方向优化选择
if (elevator.CurrentDirection == ElevatorModel.Direction.Up)
{
var upRequests = distances.Where(d => d.Request.FloorNumber >= elevator.CurrentFloor)
.OrderBy(d => d.Distance)
.ThenBy(d => d.Request.FloorNumber)
.ToList();
if (upRequests.Any())
return upRequests.First().Request.FloorNumber;
}
else if (elevator.CurrentDirection == ElevatorModel.Direction.Down)
{
var downRequests = distances.Where(d => d.Request.FloorNumber <= elevator.CurrentFloor)
.OrderByDescending(d => d.Request.FloorNumber)
.ThenBy(d => d.Distance)
.ToList();
if (downRequests.Any())
return downRequests.First().Request.FloorNumber;
}
// 默认选择距离最近的请求
return distances.OrderBy(d => d.Distance).First().Request.FloorNumber;
}
private static int SCAN(ElevatorModel elevator, List<FloorRequest> requests)
{
var pendingRequests = requests.Where(r => !r.IsCompleted).ToList();
if (!pendingRequests.Any())
return elevator.CurrentFloor;
// 根据当前方向排序请求
if (elevator.CurrentDirection == ElevatorModel.Direction.Up)
{
var upRequests = pendingRequests.Where(r => r.FloorNumber >= elevator.CurrentFloor)
.OrderBy(r => r.FloorNumber)
.ToList();
if (upRequests.Any())
{
// 找到最远的上升请求
var farthestUp = upRequests.Last();
// 检查是否有下降请求在当前楼层和最远上升请求之间
var downRequestsBetween = pendingRequests.Where(r => r.FloorNumber < farthestUp.FloorNumber && r.FloorNumber > elevator.CurrentFloor)
.OrderByDescending(r => r.FloorNumber)
.ToList();
if (downRequestsBetween.Any())
{
// 如果有,先处理最远的下降请求
return downRequestsBetween.First().FloorNumber;
}
return farthestUp.FloorNumber;
}
}
else if (elevator.CurrentDirection == ElevatorModel.Direction.Down)
{
var downRequests = pendingRequests.Where(r => r.FloorNumber <= elevator.CurrentFloor)
.OrderByDescending(r => r.FloorNumber)
.ToList();
if (downRequests.Any())
{
// 找到最远的下降请求
var farthestDown = downRequests.First();
// 检查是否有上升请求在当前楼层和最远下降请求之间
var upRequestsBetween = pendingRequests.Where(r => r.FloorNumber > farthestDown.FloorNumber && r.FloorNumber < elevator.CurrentFloor)
.OrderBy(r => r.FloorNumber)
.ToList();
if (upRequestsBetween.Any())
{
// 如果有,先处理最远的上升请求
return upRequestsBetween.Last().FloorNumber;
}
return farthestDown.FloorNumber;
}
}
// 默认行为 - 继续当前方向
if (elevator.CurrentDirection == ElevatorModel.Direction.Up)
return pendingRequests.Where(r => r.FloorNumber >= elevator.CurrentFloor)
.OrderBy(r => r.FloorNumber)
.FirstOrDefault()?.FloorNumber ?? elevator.CurrentFloor;
else
return pendingRequests.Where(r => r.FloorNumber <= elevator.CurrentFloor)
.OrderByDescending(r => r.FloorNumber)
.FirstOrDefault()?.FloorNumber ?? elevator.CurrentFloor;
}
private static int C_SCAN(ElevatorModel elevator, List<FloorRequest> requests)
{
var pendingRequests = requests.Where(r => !r.IsCompleted).ToList();
if (!pendingRequests.Any())
return elevator.CurrentFloor;
if (elevator.CurrentDirection == ElevatorModel.Direction.Up)
{
// 处理所有上升请求
var upRequests = pendingRequests.Where(r => r.FloorNumber >= elevator.CurrentFloor)
.OrderBy(r => r.FloorNumber)
.ToList();
if (upRequests.Any())
{
// 找到最远的上升请求
var farthestUp = upRequests.Last();
// 如果没有更多上升请求,切换方向到最低楼层
if (!pendingRequests.Any(r => r.FloorNumber < elevator.CurrentFloor))
{
return farthestUp.FloorNumber;
}
return farthestUp.FloorNumber;
}
}
else if (elevator.CurrentDirection == ElevatorModel.Direction.Down)
{
// 处理所有下降请求
var downRequests = pendingRequests.Where(r => r.FloorNumber <= elevator.CurrentFloor)
.OrderByDescending(r => r.FloorNumber)
.ToList();
if (downRequests.Any())
{
// 找到最远的下降请求
var farthestDown = downRequests.First();
// 如果没有更多下降请求,切换方向到最高楼层
if (!pendingRequests.Any(r => r.FloorNumber > elevator.CurrentFloor))
{
return farthestDown.FloorNumber;
}
return farthestDown.FloorNumber;
}
}
// 默认行为 - 继续当前方向
if (elevator.CurrentDirection == ElevatorModel.Direction.Up)
{
var upRequests = pendingRequests.Where(r => r.FloorNumber >= elevator.CurrentFloor)
.OrderBy(r => r.FloorNumber)
.ToList();
if (upRequests.Any())
return upRequests.Last().FloorNumber;
}
else
{
var downRequests = pendingRequests.Where(r => r.FloorNumber <= elevator.CurrentFloor)
.OrderByDescending(r => r.FloorNumber)
.ToList();
if (downRequests.Any())
return downRequests.First().FloorNumber;
}
return elevator.CurrentFloor;
}
}
4. 视图实现 (Views/MainView.xaml)
<Window x:Class="ElevatorSimulation.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ElevatorSimulation.Views"
xmlns:vm="clr-namespace:ElevatorSimulation.ViewModels"
mc:Ignorable="d"
Title="电梯调度模拟器" Height="600" Width="800">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Grid>
<!-- 电梯井道 -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 控制面板 -->
<StackPanel Grid.Column="0" Margin="10">
<TextBlock Text="电梯控制面板" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>
<ComboBox ItemsSource="{Binding Source={StaticResource AlgorithmTypes}}"
SelectedItem="{Binding CurrentAlgorithm}"
DisplayMemberPath="Name"
Margin="0,0,0,5"/>
<Button Content="开始模拟" Command="{Binding StartSimulationCommand}" Margin="0,0,0,5"/>
<Button Content="添加请求" Command="{Binding AddRequestCommand}" Margin="0,0,0,5"/>
<Button Content="重置模拟" Command="{Binding ResetSimulationCommand}" Margin="0,0,0,20"/>
<TextBlock Text="当前状态:" FontSize="14" FontWeight="Bold"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="楼层: " FontSize="12"/>
<TextBlock Text="{Binding Elevator.CurrentFloor}" FontSize="12" Foreground="Blue"/>
<TextBlock Text=" 方向: " FontSize="12"/>
<TextBlock Text="{Binding Elevator.CurrentDirection, Converter={StaticResource DirectionConverter}}" FontSize="12" Foreground="Green"/>
</StackPanel>
<TextBlock Text="待处理请求:" FontSize="14" FontWeight="Bold" Margin="0,10,0,0"/>
<ListBox ItemsSource="{Binding Requests}" Height="150">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FloorNumber}" Width="50"/>
<TextBlock Text="{Binding RequestTime, StringFormat='{}{0:HH:mm:ss}'}" Width="100"/>
<TextBlock Text="{Binding IsCompleted, Converter={StaticResource CompletionConverter}}" Foreground="{Binding IsCompleted, Converter={StaticResource CompletionColorConverter}}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
<!-- 电梯井道可视化 -->
<Grid Grid.Column="1" Margin="10">
<Grid.RowDefinitions>
<!-- 楼层显示区 -->
<RowDefinition Height="Auto"/>
<!-- 电梯井道 -->
<RowDefinition Height="*"/>
<!-- 控制按钮区 -->
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 楼层标签 -->
<ItemsControl ItemsSource="{Binding Floors}" Grid.Row="0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Number}"
FontSize="14"
Margin="5"
Foreground="{Binding IsCurrent, Converter={StaticResource CurrentFloorColorConverter}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- 电梯井道 -->
<Grid Grid.Row="1" Background="#EEE">
<!-- 电梯轿厢 -->
<Border x:Name="ElevatorCar"
Width="80"
Height="100"
Background="#4CAF50"
CornerRadius="5"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Text="{Binding Elevator.CurrentFloor}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="White"
FontSize="16"/>
</Border>
<!-- 楼层分隔线 -->
<ItemsControl ItemsSource="{Binding Floors}" Grid.Row="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="0" Y1="{Binding YPosition}"
X2="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Canvas}}"
Y2="{Binding YPosition}"
Stroke="#CCC" StrokeThickness="1"/>
<!-- 楼层按钮 -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="↑" Width="30" Height="30" Margin="0,0,5,0"
Visibility="{Binding IsAboveCurrent, Converter={StaticResource VisibilityConverter}}"
Command="{Binding Elevator.GoUpCommand, Source={StaticResource ElevatorViewModel}}"/>
<TextBlock Text="{Binding Number}" FontSize="12"/>
<Button Content="↓" Width="30" Height="30"
Visibility="{Binding IsBelowCurrent, Converter={StaticResource VisibilityConverter}}"
Command="{Binding Elevator.GoDownCommand, Source={StaticResource ElevatorViewModel}}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
<!-- 控制按钮区 -->
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="1楼" Margin="5" Command="{Binding Elevator.GoToFloorCommand, Source={StaticResource ElevatorViewModel}, CommandParameter=1}"/>
<Button Content="5楼" Margin="5" Command="{Binding Elevator.GoToFloorCommand, Source={StaticResource ElevatorViewModel}, CommandParameter=5}"/>
<Button Content="10楼" Margin="5" Command="{Binding Elevator.GoToFloorCommand, Source={StaticResource ElevatorViewModel}, CommandParameter=10}"/>
<Button Content="15楼" Margin="5" Command="{Binding Elevator.GoToFloorCommand, Source={StaticResource ElevatorViewModel}, CommandParameter=15}"/>
<Button Content="20楼" Margin="5" Command="{Binding Elevator.GoToFloorCommand, Source={StaticResource ElevatorViewModel}, CommandParameter=20}"/>
</StackPanel>
</Grid>
</Grid>
</Window>
5. 资源文件 (Resources/Converters.xaml)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ElevatorSimulation.ViewModels">
<!-- 算法类型集合 -->
<ObjectDataProvider x:Key="AlgorithmTypes" MethodName="GetValues" ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="vm:ElevatorAlgorithm"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<!-- 方向转换器 -->
<local:DirectionConverter x:Key="DirectionConverter"/>
<!-- 完成状态转换器 -->
<local:CompletionConverter x:Key="CompletionConverter"/>
<local:CompletionColorConverter x:Key="CompletionColorConverter"/>
<!-- 楼层颜色转换器 -->
<local:CurrentFloorColorConverter x:Key="CurrentFloorColorConverter"/>
<!-- 可见性转换器 -->
<BooleanToVisibilityConverter x:Key="VisibilityConverter"/>
</ResourceDictionary>
6. 算法可视化实现 (ViewModels/ElevatorViewModel.cs)
public class ElevatorViewModel : INotifyPropertyChanged
{
private List<Floor> _floors;
private ElevatorModel _elevator;
public event PropertyChangedEventHandler PropertyChanged;
public List<Floor> Floors
{
get => _floors;
set
{
_floors = value;
OnPropertyChanged();
}
}
public ElevatorModel Elevator
{
get => _elevator;
set
{
_elevator = value;
OnPropertyChanged();
}
}
public ICommand GoToFloorCommand { get; }
public ElevatorViewModel()
{
// 初始化电梯
Elevator = new ElevatorModel();
// 初始化楼层 (1-20层)
Floors = Enumerable.Range(1, 20).Select(i => new Floor
{
Number = i,
YPosition = (i - 1) * 50, // 每层50像素
IsCurrent = i == Elevator.CurrentFloor,
IsAboveCurrent = i > Elevator.CurrentFloor,
IsBelowCurrent = i < Elevator.CurrentFloor
}).ToList();
// 命令绑定
GoToFloorCommand = new RelayCommand<int>(GoToFloor);
}
private void GoToFloor(int floorNumber)
{
// 设置目标楼层
var targetFloor = Floors.FirstOrDefault(f => f.Number == floorNumber);
if (targetFloor != null)
{
// 更新电梯状态
Elevator.CurrentFloor = floorNumber;
// 更新楼层显示
UpdateFloorDisplay();
}
}
private void UpdateFloorDisplay()
{
foreach (var floor in Floors)
{
floor.IsCurrent = floor.Number == Elevator.CurrentFloor;
floor.IsAboveCurrent = floor.Number > Elevator.CurrentFloor;
floor.IsBelowCurrent = floor.Number < Elevator.CurrentFloor;
}
OnPropertyChanged(nameof(Floors));
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Floor
{
public int Number { get; set; }
public double YPosition { get; set; }
public bool IsCurrent { get; set; }
public bool IsAboveCurrent { get; set; }
public bool IsBelowCurrent { get; set; }
}
7. 转换器实现 (Converters/DirectionConverter.cs)
public class DirectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ElevatorModel.Direction direction)
{
switch (direction)
{
case ElevatorModel.Direction.Up:
return "↑";
case ElevatorModel.Direction.Down:
return "↓";
default:
return "";
}
}
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
8. 主程序入口 (App.xaml.cs)
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 初始化资源
var resourceDictionary = new ResourceDictionary
{
Source = new Uri("Resources/Converters.xaml", UriKind.Relative)
};
// 合并资源字典
Resources.MergedDictionaries.Add(resourceDictionary);
// 启动主窗口
var mainWindow = new Views.MainView();
mainWindow.Show();
}
}
三、功能扩展建议
- 多电梯支持:扩展系统以支持多个电梯协同工作
- 智能调度:实现更复杂的调度算法,如遗传算法优化
- 能耗分析:添加电梯能耗计算和显示功能
- 用户交互:允许用户通过界面按钮呼叫电梯
- 性能监控:添加实时性能指标显示,如平均等待时间
- 数据记录:记录模拟运行数据以便分析
- 3D可视化:使用Helix Toolkit等库实现3D电梯井道可视化
四、技术要点总结
- MVVM模式:采用Model-View-ViewModel架构分离业务逻辑和界面
- 数据绑定:利用WPF强大的数据绑定机制实现界面动态更新
- 命令绑定:使用ICommand接口实现界面交互逻辑
- 转换器:自定义IValueConverter实现数据格式转换
- 资源管理:集中管理样式、模板和转换器等资源
- 动画效果:可添加电梯移动动画增强用户体验
这个实现提供了一个完整的电梯调度模拟器框架,可以根据需要进一步扩展和完善功能。