diff --git a/README.md b/README.md
index bd506176..a5074b3d 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,52 @@
# Lift-Simulation
-Create a web app where you can simulate lift mechanics for a client
-
-# UI Example
-
-
-# Requirements
- 1. Have a page where you input the number of floors and lifts from the user
- 2. An interactive UI is generated, where we have visual depictions of lifts and buttons on floors
- 3. Upon clicking a particular button on the floor, a lift goes to that floor
-
- Milestone 1:
- - Data store that contains the state of your application data
- - JS Engine that is the controller for which lift goes where
- - Dumb UI that responds to controller's commands
-
- Milestone 2:
- - Lift having doors open in 2.5s, then closing in another 2.5s
- - Lift moving at 2s per floor
- - Lift stopping at every floor where it was called
- - Mobile friendly design
+
+Create a web app where you can simulate lift mechanics for a client.
+
+## Features
+
+1. Configure the number of floors (2-10) and lifts (1-5)
+2. Interactive UI with visual depictions of lifts and call buttons on floors
+3. Advanced lift scheduling algorithm for efficient operation
+4. Smooth animations for lift movement and door operations
+5. Visual and audio feedback for lift operations
+6. Floor indicators and direction indicators inside lifts
+7. Lift malfunction simulation with emergency controls
+8. Detailed status display for each lift
+9. Mobile responsive design
+10. Sound effects for enhanced user experience (when sound files are available)
+
+## Requirements (Completed)
+
+1. Have a page where you input the number of floors and lifts from the user ✓
+2. An interactive UI is generated, where we have visual depictions of lifts and buttons on floors ✓
+3. Upon clicking a particular button on the floor, a lift goes to that floor ✓
+
+### Milestone 1 (Completed)
+
+- Data store that contains the state of your application data ✓
+- JS Engine that is the controller for which lift goes where ✓
+- Dumb UI that responds to controller's commands ✓
+
+### Milestone 2 (Completed)
+
+- Lift having doors open in 2.5s, then closing in another 2.5s ✓
+- Lift moving at 2s per floor ✓
+- Lift stopping at every floor where it was called ✓
+- Mobile friendly design ✓
+
+### Enhanced Features (New)
+
+- Smooth animations with cubic-bezier transitions
+- Improved lift allocation algorithm using SCAN method
+- Visual feedback for lift arrival and movement
+- Sound effects for buttons, doors, movement, and arrival
+- Floor and direction indicators inside lifts
+- Enhanced status display with visual cues
+
+## UI
+
+
+
+
+
+
\ No newline at end of file
diff --git a/image-1.png b/image-1.png
new file mode 100644
index 00000000..fb648108
Binary files /dev/null and b/image-1.png differ
diff --git a/image-3.png b/image-3.png
new file mode 100644
index 00000000..59156249
Binary files /dev/null and b/image-3.png differ
diff --git a/image.png b/image.png
new file mode 100644
index 00000000..3037f175
Binary files /dev/null and b/image.png differ
diff --git a/src/css/main.css b/src/css/main.css
index e69de29b..80174525 100644
--- a/src/css/main.css
+++ b/src/css/main.css
@@ -0,0 +1,632 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ min-height: 100vh;
+ color: #333;
+}
+
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 20px;
+}
+
+header {
+ text-align: center;
+ margin-bottom: 30px;
+}
+
+header h1 {
+ color: white;
+ font-size: 2.5rem;
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
+}
+
+/* Configuration Panel */
+.config-panel {
+ background: rgba(255, 255, 255, 0.95);
+ padding: 20px;
+ border-radius: 15px;
+ margin-bottom: 20px;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.input-group {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+}
+
+.input-group label {
+ font-weight: 600;
+ color: #555;
+}
+
+.input-group input {
+ padding: 10px;
+ border: 2px solid #ddd;
+ border-radius: 8px;
+ font-size: 16px;
+ transition: border-color 0.3s;
+}
+
+.input-group input:focus {
+ outline: none;
+ border-color: #667eea;
+}
+
+#generate-btn {
+ background: linear-gradient(45deg, #667eea, #764ba2);
+ color: white;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 8px;
+ font-size: 16px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: transform 0.2s;
+}
+
+#generate-btn:hover {
+ transform: translateY(-2px);
+}
+
+/* Malfunction Panel */
+.malfunction-panel {
+ background: rgba(255, 255, 255, 0.95);
+ padding: 20px;
+ border-radius: 15px;
+ margin-bottom: 20px;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
+}
+
+.malfunction-panel h3 {
+ margin-bottom: 15px;
+ color: #555;
+ text-align: center;
+}
+
+.malfunction-controls {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ justify-content: center;
+ flex-wrap: wrap;
+ margin-bottom: 15px;
+}
+
+#malfunction-lift-id {
+ padding: 10px;
+ border: 2px solid #ddd;
+ border-radius: 8px;
+ font-size: 18px;
+ width: 150px;
+ text-align: center;
+ font-weight: bold;
+ color: #333;
+ background: linear-gradient(to bottom, #f9f9f9, #e9e9e9);
+}
+
+#malfunction-lift-id:focus {
+ border-color: #3498db;
+ box-shadow: 0 0 10px rgba(52, 152, 219, 0.5);
+ outline: none;
+}
+
+.malfunction-controls label {
+ font-weight: bold;
+ margin-right: 10px;
+}
+
+#disable-lift-btn {
+ background: linear-gradient(45deg, #ff6b6b, #ee5a24);
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 8px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: transform 0.2s;
+}
+
+#enable-all-lifts-btn {
+ background: linear-gradient(45deg, #00b894, #00a085);
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 8px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: transform 0.2s;
+}
+
+#disable-lift-btn:hover,
+#enable-all-lifts-btn:hover {
+ transform: translateY(-2px);
+}
+
+.status-display {
+ text-align: center;
+ padding: 10px;
+ border-radius: 8px;
+ font-weight: 600;
+ min-height: 20px;
+}
+
+.status-display.error {
+ background: #ffe6e6;
+ color: #d63031;
+ border: 1px solid #ff7675;
+}
+
+.status-display.success {
+ background: #e6ffe6;
+ color: #00b894;
+ border: 1px solid #00cec9;
+}
+
+/* Building and Lift Styles */
+.simulation-container {
+ background: rgba(255, 255, 255, 0.95);
+ padding: 30px;
+ border-radius: 15px;
+ margin-bottom: 20px;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
+}
+
+.building {
+ display: flex;
+ flex-direction: column-reverse;
+ border: 3px solid #333;
+ border-radius: 10px;
+ background: #f8f9fa;
+ overflow: hidden;
+ max-width: 800px;
+ margin: 0 auto;
+}
+
+.floor {
+ display: flex;
+ height: 100px;
+ border-bottom: 2px solid #333;
+ position: relative;
+ background: linear-gradient(90deg, #e9ecef 0%, #f8f9fa 100%);
+}
+
+.floor:last-child {
+ border-bottom: none;
+}
+
+.floor-info {
+ width: 80px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ background: #495057;
+ color: white;
+ font-weight: bold;
+ border-right: 2px solid #333;
+}
+
+.floor-number {
+ font-size: 18px;
+ margin-bottom: 5px;
+}
+
+.call-button {
+ background: linear-gradient(45deg, #74b9ff, #0984e3);
+ color: white;
+ border: none;
+ padding: 8px 12px;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 12px;
+ font-weight: 600;
+ transition: all 0.3s;
+}
+
+.call-button:hover {
+ background: linear-gradient(45deg, #0984e3, #74b9ff);
+ transform: scale(1.05);
+}
+
+.call-button.active {
+ background: linear-gradient(45deg, #fdcb6e, #e17055);
+ animation: pulse 1s infinite;
+}
+
+@keyframes pulse {
+ 0% { transform: scale(1); }
+ 50% { transform: scale(1.1); }
+ 100% { transform: scale(1); }
+}
+
+.lift-shafts {
+ flex: 1;
+ display: flex;
+ position: relative;
+}
+
+.lift-shaft {
+ flex: 1;
+ border-right: 2px solid #333;
+ position: relative;
+ background: linear-gradient(180deg, #dee2e6 0%, #adb5bd 100%);
+}
+
+.lift-shaft:last-child {
+ border-right: none;
+}
+
+.lift {
+ position: absolute;
+ bottom: 0;
+ left: 5px;
+ right: 5px;
+ height: 90px;
+ background: linear-gradient(45deg, #2d3436, #636e72);
+ border: 3px solid #555;
+ border-radius: 8px;
+ transition: bottom 2s cubic-bezier(0.33, 1, 0.68, 1);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 4px 15px rgba(0,0,0,0.3);
+}
+
+.lift.moving-up {
+ animation: liftBounceUp 2s cubic-bezier(0.33, 1, 0.68, 1);
+}
+
+.lift.moving-down {
+ animation: liftBounceDown 2s cubic-bezier(0.33, 1, 0.68, 1);
+}
+
+@keyframes liftBounceUp {
+ 0% { transform: translateY(0); }
+ 10% { transform: translateY(5px); }
+ 100% { transform: translateY(0); }
+}
+
+@keyframes liftBounceDown {
+ 0% { transform: translateY(0); }
+ 10% { transform: translateY(-5px); }
+ 100% { transform: translateY(0); }
+}
+
+.lift.disabled {
+ border: 3px solid #d63031;
+ background: linear-gradient(45deg, #d63031, #e17055);
+ animation: malfunction-blink 1s infinite alternate;
+}
+
+@keyframes malfunction-blink {
+ 0% { box-shadow: 0 0 5px #d63031; }
+ 100% { box-shadow: 0 0 20px #d63031, 0 0 30px #d63031; }
+}
+
+.lift-doors {
+ width: 80%;
+ height: 70%;
+ background: #2d3436;
+ border-radius: 4px;
+ position: relative;
+ overflow: hidden;
+}
+
+.lift-door {
+ position: absolute;
+ top: 0;
+ width: 50%;
+ height: 100%;
+ background: linear-gradient(90deg, #74b9ff, #0984e3);
+ transition: transform 2.5s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+.lift-door.left {
+ left: 0;
+ border-right: 1px solid #333;
+}
+
+.lift-door.right {
+ right: 0;
+ border-left: 1px solid #333;
+}
+
+.lift-doors.open .lift-door.left {
+ transform: translateX(-100%);
+}
+
+.lift-doors.open .lift-door.right {
+ transform: translateX(100%);
+}
+
+.lift-id {
+ position: absolute;
+ top: 5px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: linear-gradient(45deg, #f39c12, #e74c3c);
+ color: white;
+ padding: 4px 12px;
+ border-radius: 20px;
+ font-size: 14px;
+ font-weight: bold;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
+ z-index: 10;
+ letter-spacing: 1px;
+ text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3);
+ border: 2px solid white;
+}
+
+/* Direction and Floor indicators */
+.lift-indicators {
+ position: absolute;
+ bottom: 10px;
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+}
+
+.direction-indicator {
+ background-color: rgba(0, 0, 0, 0.7);
+ border-radius: 50%;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: white;
+ font-weight: bold;
+}
+
+.direction-indicator.up {
+ color: #00b894;
+}
+
+.direction-indicator.down {
+ color: #e17055;
+}
+
+.floor-indicator {
+ background-color: rgba(0, 0, 0, 0.7);
+ border-radius: 4px;
+ padding: 3px 10px;
+ color: #ffeaa7;
+ font-weight: bold;
+ font-size: 14px;
+}
+
+/* Add visual feedback to moving lifts */
+.lift-shaft {
+ position: relative;
+ overflow: visible;
+}
+
+.floor-arrival-indicator {
+ position: absolute;
+ width: 100%;
+ height: 8px;
+ bottom: 0;
+ background: rgba(0, 184, 148, 0.3);
+ transform: scaleX(0);
+ transform-origin: left;
+ transition: transform 1s ease-in-out;
+ z-index: 1;
+}
+
+.floor-arrival-indicator.arriving {
+ transform: scaleX(1);
+}
+
+
+
+/* Status Panel */
+.status-panel {
+ background: rgba(255, 255, 255, 0.95);
+ padding: 20px;
+ border-radius: 15px;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
+}
+
+.status-panel h3 {
+ margin-bottom: 15px;
+ color: #555;
+ text-align: center;
+}
+
+.lift-status-container {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 15px;
+}
+
+/* Enhanced Status Cards */
+.lift-status-card {
+ background: #f8f9fa;
+ padding: 15px;
+ border-radius: 10px;
+ border-left: 4px solid #74b9ff;
+ box-shadow: 0 4px 6px rgba(0,0,0,0.05);
+ transition: transform 0.3s, box-shadow 0.3s;
+}
+
+.lift-status-card:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 8px 15px rgba(0,0,0,0.1);
+}
+
+.lift-status-card.disabled {
+ border-left-color: #d63031;
+ background: #ffe6e6;
+}
+
+.lift-status-card h4 {
+ margin-bottom: 15px;
+ color: #333;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-bottom: 10px;
+ border-bottom: 1px solid rgba(0,0,0,0.1);
+}
+
+.status-badge {
+ font-size: 12px;
+ padding: 3px 8px;
+ border-radius: 20px;
+ font-weight: normal;
+ color: white;
+}
+
+.status-moving {
+ background: #74b9ff;
+}
+
+.status-idle {
+ background: #95a5a6;
+}
+
+.status-door-open {
+ background: #00b894;
+}
+
+.status-malfunction {
+ background: #d63031;
+}
+
+.status-row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+ gap: 10px;
+}
+
+.status-icon {
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 14px;
+}
+
+.floor-number-badge {
+ display: inline-block;
+ background: #2d3436;
+ color: white;
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ text-align: center;
+ line-height: 24px;
+ margin-left: 5px;
+ font-weight: bold;
+}
+
+.malfunction-warning {
+ background-color: rgba(214, 48, 49, 0.1);
+ border-left: 3px solid #d63031;
+ padding: 8px;
+ margin-top: 10px;
+ color: #d63031;
+ font-weight: bold;
+ display: flex;
+ align-items: center;
+ gap: 5px;
+}
+
+.lift-number-indicator {
+ display: inline-block;
+ padding: 5px 10px;
+ border-radius: 5px;
+ color: white;
+ font-weight: bold;
+ margin-right: 10px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+ letter-spacing: 1px;
+ text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3);
+ font-size: 16px;
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+ .container {
+ padding: 10px;
+ }
+
+ .config-panel {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .malfunction-controls {
+ flex-direction: column;
+ }
+
+ #malfunction-lift-id {
+ width: 100%;
+ }
+
+ .building {
+ max-width: 100%;
+ }
+
+ .floor {
+ height: 80px;
+ }
+
+ .lift {
+ height: 70px;
+ }
+
+ .floor-info {
+ width: 60px;
+ }
+}
+
+.lift-shaft::before {
+ content: attr(data-lift-id);
+ position: absolute;
+ top: 5px;
+ left: 5px;
+ background-color: rgba(0, 0, 0, 0.5);
+ color: white;
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ font-weight: bold;
+ z-index: 1;
+}
+
+.lift-shaft-label {
+ position: absolute;
+ top: -25px;
+ left: 50%;
+ transform: translateX(-50%);
+ background-color: #333;
+ color: white;
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: bold;
+ z-index: 5;
+}
+
diff --git a/src/index.html b/src/index.html
index e69de29b..bd0598b9 100644
--- a/src/index.html
+++ b/src/index.html
@@ -0,0 +1,57 @@
+
+
+
+
+
+ Lift Simulation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Lift Malfunction Controls
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/js/main.js b/src/js/main.js
index e69de29b..9123f7a2 100644
--- a/src/js/main.js
+++ b/src/js/main.js
@@ -0,0 +1,621 @@
+class LiftSimulation {
+ constructor() {
+ this.lifts = [];
+ this.floors = 0;
+ this.liftCount = 0;
+ this.floorRequests = new Set();
+ this.isSimulationActive = false;
+
+ this.initializeEventListeners();
+ }initializeEventListeners() {
+
+ document.getElementById('generate-btn').addEventListener('click', () => {
+ this.generateSimulation();
+ });
+
+ document.getElementById('disable-lift-btn').addEventListener('click', () => {
+ this.disableLift();
+ }); document.getElementById('enable-all-lifts-btn').addEventListener('click', () => {
+ this.enableAllLifts();
+ });
+ }
+
+ generateSimulation() {
+ const floorsInput = document.getElementById('floors');
+ const liftsInput = document.getElementById('lifts');
+
+ this.floors = parseInt(floorsInput.value);
+ this.liftCount = parseInt(liftsInput.value);
+
+ if (this.floors < 2 || this.floors > 10) {
+ this.showStatus('Please enter 2-10 floors', 'error');
+ return;
+ }
+
+ if (this.liftCount < 1 || this.liftCount > 5) {
+ this.showStatus('Please enter 1-5 lifts', 'error');
+ return;
+ }
+
+ this.initializeLifts();
+ this.createBuilding();
+ this.updateLiftStatusDisplay();
+ this.isSimulationActive = true;
+ this.showStatus('Simulation generated successfully!', 'success');
+ }
+
+ initializeLifts() {
+ this.lifts = [];
+ for (let i = 1; i <= this.liftCount; i++) {
+ this.lifts.push({
+ id: i,
+ currentFloor: 0,
+ targetFloors: [],
+ doorOpen: false,
+ disabled: false,
+ isMoving: false,
+ direction: null // 'up', 'down', or null
+ });
+ }
+ } createBuilding() {
+ const buildingContainer = document.getElementById('building');
+ buildingContainer.innerHTML = ''; for (let floor = this.floors - 1; floor >= 0; floor--) {
+ const floorDiv = document.createElement('div');
+ floorDiv.className = 'floor';
+ floorDiv.setAttribute('data-floor', floor);
+
+ // Calculate display floor number (reversed)
+ const displayFloor = this.floors - floor - 1;
+
+ // Floor info section
+ const floorInfo = document.createElement('div');
+ floorInfo.className = 'floor-info';
+
+ const floorNumber = document.createElement('div');
+ floorNumber.className = 'floor-number';
+ floorNumber.textContent = displayFloor === 0 ? 'G' : displayFloor;
+
+ const callButton = document.createElement('button');
+ callButton.className = 'call-button';
+ callButton.textContent = 'Call';
+ callButton.addEventListener('click', () => this.callLift(floor));
+
+ floorInfo.appendChild(floorNumber);
+ floorInfo.appendChild(callButton); // Lift shafts section
+ const liftShafts = document.createElement('div');
+ liftShafts.className = 'lift-shafts';
+
+ for (let liftId = 1; liftId <= this.lifts.length; liftId++) {
+ const shaft = document.createElement('div');
+ shaft.className = 'lift-shaft';
+ shaft.setAttribute('data-lift-id', liftId);
+
+ // Add shaft label on top floor
+ if (floor === this.floors - 1) {
+ const shaftLabel = document.createElement('div');
+ shaftLabel.className = 'lift-shaft-label';
+ shaftLabel.textContent = `Lift ${liftId}`;
+ shaft.appendChild(shaftLabel);
+ }
+
+ // Create lift element on ground floor for each lift
+ if (floor === 0) {
+ const liftData = this.lifts.find(l => l.id === liftId);
+ if (liftData && liftData.currentFloor === floor) {
+ const lift = this.createLiftElement(liftId);
+ shaft.appendChild(lift);
+ }
+ }
+
+ liftShafts.appendChild(shaft);
+ }
+
+ floorDiv.appendChild(floorInfo);
+ floorDiv.appendChild(liftShafts);
+ buildingContainer.appendChild(floorDiv);
+ }
+ } createLiftElement(liftId) {
+ const liftData = this.lifts.find(l => l.id === liftId);
+ const liftColor = this.getLiftColor(liftId);
+
+ const lift = document.createElement('div');
+ lift.className = 'lift';
+ lift.setAttribute('data-lift-id', liftId);
+ // Apply custom color to lift background
+ lift.style.background = liftColor.bg;
+ lift.style.borderColor = `rgba(255, 255, 255, 0.7)`;
+
+ const liftIdLabel = document.createElement('div');
+ liftIdLabel.className = 'lift-id';
+ liftIdLabel.textContent = `LIFT ${liftId}`;
+ // Add a title attribute for accessibility
+ liftIdLabel.setAttribute('title', `Elevator ${liftId}`);
+
+ const doors = document.createElement('div');
+ doors.className = 'lift-doors';
+
+ const leftDoor = document.createElement('div');
+ leftDoor.className = 'lift-door left';
+ // Apply custom color to doors
+ leftDoor.style.background = liftColor.door;
+
+ const rightDoor = document.createElement('div');
+ rightDoor.className = 'lift-door right';
+ // Apply custom color to doors
+ rightDoor.style.background = liftColor.door; doors.appendChild(leftDoor);
+ doors.appendChild(rightDoor);
+ lift.appendChild(liftIdLabel);
+ lift.appendChild(doors);
+
+ // Add indicators
+ const indicators = document.createElement('div');
+ indicators.className = 'lift-indicators';
+ const floorIndicator = document.createElement('div');
+ floorIndicator.className = 'floor-indicator';
+ const displayFloor = this.floors - liftData.currentFloor - 1;
+ floorIndicator.textContent = displayFloor === 0 ? 'G' : displayFloor;
+
+ const upIndicator = document.createElement('div');
+ upIndicator.className = 'direction-indicator up';
+ upIndicator.innerHTML = '▲'; // Unicode up arrow
+ upIndicator.style.opacity = liftData.direction === 'up' ? '1' : '0.3';
+
+ const downIndicator = document.createElement('div');
+ downIndicator.className = 'direction-indicator down';
+ downIndicator.innerHTML = '▼'; // Unicode down arrow
+ downIndicator.style.opacity = liftData.direction === 'down' ? '1' : '0.3';
+
+ indicators.appendChild(upIndicator);
+ indicators.appendChild(floorIndicator);
+ indicators.appendChild(downIndicator);
+ lift.appendChild(indicators);
+
+ return lift;
+ } async callLift(targetFloor) {
+ if (!this.isSimulationActive) {
+ this.showStatus('Please generate simulation first', 'error');
+ return;
+ }
+
+ // Add visual feedback to button
+ const callButton = document.querySelector(`[data-floor="${targetFloor}"] .call-button`);
+ callButton.classList.add('active');
+
+ // Find the best available lift
+ const bestLift = this.findBestLift(targetFloor);
+
+ if (!bestLift) {
+ this.showStatus('No lifts available at the moment', 'error');
+ setTimeout(() => {
+ callButton.classList.remove('active');
+ }, 2000);
+ return;
+ }
+
+ // Add target floor to lift's queue using the SCAN algorithm for better efficiency
+ if (!bestLift.targetFloors.includes(targetFloor)) {
+ bestLift.targetFloors.push(targetFloor);
+
+ // Set initial direction if not already set
+ if (!bestLift.direction) {
+ bestLift.direction = targetFloor > bestLift.currentFloor ? 'up' : 'down';
+ }
+
+ // Sort target floors according to the SCAN algorithm (serve floors in current direction first)
+ this.reorderTargetFloors(bestLift);
+ }
+
+ // Start moving the lift if it's not already moving
+ if (!bestLift.isMoving) {
+ this.moveLift(bestLift);
+ } // Show floor request status with the display floor number
+ const displayFloor = this.floors - targetFloor - 1;
+ this.showStatus(`Lift ${bestLift.id} is heading to floor ${displayFloor === 0 ? 'G' : displayFloor}`, 'success');
+ this.updateLiftStatusDisplay();
+ }
+
+ reorderTargetFloors(lift) {
+ const currentFloor = lift.currentFloor;
+ const direction = lift.direction;
+
+ if (direction === 'up') {
+ // Floors above current floor, in ascending order
+ const floorsAbove = lift.targetFloors.filter(floor => floor > currentFloor).sort((a, b) => a - b);
+ // Floors below current floor, in descending order
+ const floorsBelow = lift.targetFloors.filter(floor => floor < currentFloor).sort((a, b) => b - a);
+ // Floors at current floor
+ const floorsAt = lift.targetFloors.filter(floor => floor === currentFloor);
+
+ lift.targetFloors = [...floorsAbove, ...floorsBelow, ...floorsAt];
+ } else if (direction === 'down') {
+ // Floors below current floor, in descending order
+ const floorsBelow = lift.targetFloors.filter(floor => floor < currentFloor).sort((a, b) => b - a);
+ // Floors above current floor, in ascending order
+ const floorsAbove = lift.targetFloors.filter(floor => floor > currentFloor).sort((a, b) => a - b);
+ // Floors at current floor
+ const floorsAt = lift.targetFloors.filter(floor => floor === currentFloor);
+
+ lift.targetFloors = [...floorsBelow, ...floorsAbove, ...floorsAt];
+ }
+ } findBestLift(targetFloor) {
+ const availableLifts = this.lifts.filter(lift => !lift.disabled);
+
+ if (availableLifts.length === 0) return null;
+
+ // Calculate score for each lift to find the most efficient one
+ const liftScores = availableLifts.map(lift => {
+ let score = 0;
+ const distance = Math.abs(lift.currentFloor - targetFloor);
+
+ // Base score: lower is better
+ score += distance * 2; // Distance is the primary factor
+
+ // Adjust score based on lift's current status
+ if (lift.isMoving) {
+ score += 3; // Moving lifts are less preferred
+
+ // Check if the lift is moving in the same direction
+ if (lift.direction === 'up' && targetFloor > lift.currentFloor) {
+ score -= 2; // Prefer lifts already moving in target direction
+ } else if (lift.direction === 'down' && targetFloor < lift.currentFloor) {
+ score -= 2; // Prefer lifts already moving in target direction
+ } else {
+ score += 5; // Penalize lifts moving in opposite direction
+ }
+
+ // Consider the number of stops in the queue
+ score += lift.targetFloors.length;
+ }
+
+ return { lift, score };
+ });
+
+ // Sort by score (lower is better) and return the best lift
+ liftScores.sort((a, b) => a.score - b.score);
+ return liftScores[0].lift;
+ }
+
+ async moveLift(lift) {
+ if (lift.disabled || lift.targetFloors.length === 0) return;
+
+ lift.isMoving = true;
+
+ while (lift.targetFloors.length > 0 && !lift.disabled) {
+ const targetFloor = lift.targetFloors.shift();
+
+ // Determine direction
+ if (targetFloor > lift.currentFloor) {
+ lift.direction = 'up';
+ } else if (targetFloor < lift.currentFloor) {
+ lift.direction = 'down';
+ }
+
+ // Move to target floor
+ await this.animateLiftMovement(lift, targetFloor);
+
+ if (lift.disabled) break;
+
+ // Open doors
+ await this.operateDoors(lift, true);
+
+ // Keep doors open for 2.5 seconds
+ await this.delay(2500);
+
+ if (lift.disabled) break;
+
+ // Close doors
+ await this.operateDoors(lift, false);
+
+ // Remove active state from call button
+ const callButton = document.querySelector(`[data-floor="${targetFloor}"] .call-button`);
+ if (callButton) {
+ callButton.classList.remove('active');
+ }
+
+ this.updateLiftStatusDisplay();
+ }
+
+ lift.isMoving = false;
+ lift.direction = null;
+ this.updateLiftStatusDisplay();
+ } async animateLiftMovement(lift, targetFloor) {
+ const startFloor = lift.currentFloor;
+ const distance = Math.abs(targetFloor - startFloor);
+ const direction = targetFloor > startFloor ? 1 : -1;
+
+ // Add direction class for animation
+ const liftElement = document.querySelector(`[data-lift-id="${lift.id}"]`);
+ if (liftElement) {
+ if (direction > 0) {
+ liftElement.classList.add('moving-up');
+ } else {
+ liftElement.classList.add('moving-down');
+ }
+
+ // Remove animation class after it completes
+ setTimeout(() => {
+ liftElement.classList.remove('moving-up', 'moving-down');
+ }, 2000);
+ }
+
+ for (let i = 1; i <= distance; i++) {
+ if (lift.disabled) break;
+
+ // Show floor arrival indicator before reaching the next floor
+ const nextFloor = startFloor + (i * direction);
+ const nextFloorShaft = document.querySelector(`[data-floor="${nextFloor}"] .lift-shaft[data-lift-id="${lift.id}"]`);
+
+ if (nextFloorShaft) {
+ // Create or get floor arrival indicator
+ let indicator = nextFloorShaft.querySelector('.floor-arrival-indicator');
+ if (!indicator) {
+ indicator = document.createElement('div');
+ indicator.className = 'floor-arrival-indicator';
+ nextFloorShaft.appendChild(indicator);
+ }
+
+ indicator.classList.add('arriving');
+
+ // Remove indicator after animation
+ setTimeout(() => {
+ indicator.classList.remove('arriving');
+ }, 1000);
+ }
+ await this.delay(2000); // 2 seconds per floor
+
+ lift.currentFloor = nextFloor;
+ this.updateLiftPosition(lift);
+ this.updateLiftStatusDisplay();
+
+ }
+ } updateLiftPosition(lift) {
+ // Find all lift elements for this lift ID across all floors
+ const allLiftElements = document.querySelectorAll(`[data-lift-id="${lift.id}"]`);
+
+ allLiftElements.forEach(liftElement => {
+ if (liftElement.classList.contains('lift')) {
+ // Remove lift from current position
+ liftElement.remove();
+ }
+ });
+
+ // Create lift element on current floor
+ const currentFloorShaft = document.querySelector(`[data-floor="${lift.currentFloor}"] .lift-shaft[data-lift-id="${lift.id}"]`);
+ if (currentFloorShaft) {
+ const newLiftElement = this.createLiftElement(lift.id);
+ if (lift.disabled) {
+ newLiftElement.classList.add('disabled');
+ }
+ if (lift.doorOpen) {
+ const doorsElement = newLiftElement.querySelector('.lift-doors');
+ if (doorsElement) {
+ doorsElement.classList.add('open');
+ }
+ }
+
+ // Update the floor indicator inside the lift to show the reversed floor number
+ const floorIndicator = newLiftElement.querySelector('.floor-indicator');
+ if (floorIndicator) {
+ const displayFloor = this.floors - lift.currentFloor - 1;
+ floorIndicator.textContent = displayFloor === 0 ? 'G' : displayFloor;
+ }
+
+ currentFloorShaft.appendChild(newLiftElement);
+ }
+ }async operateDoors(lift, open) {
+ const liftElement = document.querySelector(`[data-lift-id="${lift.id}"]`);
+ if (!liftElement) return;
+
+ const doorsElement = liftElement.querySelector('.lift-doors');
+ if (!doorsElement) return;
+ if (open) {
+ doorsElement.classList.add('open');
+ lift.doorOpen = true;
+ } else {
+ doorsElement.classList.remove('open');
+ lift.doorOpen = false;
+ }// Update indicators
+ const floorIndicator = liftElement.querySelector('.floor-indicator');
+ if (floorIndicator) {
+ const displayFloor = this.floors - lift.currentFloor - 1;
+ floorIndicator.textContent = displayFloor === 0 ? 'G' : displayFloor;
+ }
+
+ const upIndicator = liftElement.querySelector('.direction-indicator.up');
+ if (upIndicator) {
+ upIndicator.style.opacity = lift.direction === 'up' ? '1' : '0.3';
+ }
+
+ const downIndicator = liftElement.querySelector('.direction-indicator.down');
+ if (downIndicator) {
+ downIndicator.style.opacity = lift.direction === 'down' ? '1' : '0.3';
+ }
+
+ await this.delay(2500); // 2.5 seconds for door operation
+ }
+
+ async disableLift() {
+ const liftIdInput = document.getElementById('malfunction-lift-id');
+ const liftId = parseInt(liftIdInput.value);
+
+ if (!liftId || liftId < 1 || liftId > this.lifts.length) {
+ this.showStatus('Please enter a valid lift number', 'error');
+ return;
+ }
+
+ const lift = this.lifts.find(l => l.id === liftId);
+ if (!lift) {
+ this.showStatus('Lift not found', 'error');
+ return;
+ }
+
+ if (lift.disabled) {
+ this.showStatus('Lift is already disabled', 'error');
+ return;
+ }
+
+ // Disable the lift
+ lift.disabled = true;
+ lift.targetFloors = []; // Clear pending requests
+
+ // Move to nearest floor (current floor is already nearest)
+ const nearestFloor = lift.currentFloor;
+ // Apply visual cue (red border)
+ const liftElement = document.querySelector(`[data-lift-id="${lift.id}"]`);
+ liftElement.classList.add('disabled');
+
+ // Open doors and keep them open
+ await this.operateDoors(lift, true);
+
+ const displayNearestFloor = this.floors - nearestFloor - 1;
+ const floorDisplay = displayNearestFloor === 0 ? 'G' : displayNearestFloor;
+ this.showStatus(`Lift ${liftId} has been disabled and moved to floor ${floorDisplay}`, 'success');
+ this.updateLiftStatusDisplay();
+
+ // Clear input
+ liftIdInput.value = '';
+ }
+
+ enableAllLifts() {
+ let disabledCount = 0;
+
+ this.lifts.forEach(lift => {
+ if (lift.disabled) {
+ lift.disabled = false;
+ disabledCount++;
+
+ // Remove visual cue
+ const liftElement = document.querySelector(`[data-lift-id="${lift.id}"]`);
+ liftElement.classList.remove('disabled');
+
+ // Close doors
+ this.operateDoors(lift, false);
+ }
+ });
+
+ if (disabledCount > 0) {
+ this.showStatus(`${disabledCount} lift(s) have been re-enabled`, 'success');
+ } else {
+ this.showStatus('No disabled lifts found', 'error');
+ }
+
+ this.updateLiftStatusDisplay();
+ } updateLiftStatusDisplay() {
+ const container = document.getElementById('lift-status');
+ container.innerHTML = '';
+
+ this.lifts.forEach(lift => {
+ const card = document.createElement('div');
+ card.className = `lift-status-card ${lift.disabled ? 'disabled' : ''}`;
+
+ const status = lift.disabled ? 'MALFUNCTIONED' :
+ lift.isMoving ? 'MOVING' :
+ lift.doorOpen ? 'DOORS OPEN' : 'IDLE'; // Format target floors for better readability with reversed numbering
+ const formatFloorNumber = (floor) => {
+ const displayFloor = this.floors - floor - 1;
+ return displayFloor === 0 ? 'G' : displayFloor;
+ };
+
+ let targetFloorsText = 'No pending requests';
+ if (lift.targetFloors.length > 0) {
+ // Create a more visual representation of the queue
+ targetFloorsText = lift.targetFloors.map(floor => {
+ const icon = floor > lift.currentFloor ? '↑' :
+ floor < lift.currentFloor ? '↓' : '•';
+ return `${icon} ${formatFloorNumber(floor)}`;
+ }).join(', ');
+ }
+
+ // Add visual indicators for direction and status
+ const directionIcon = lift.direction === 'up' ? '↑' :
+ lift.direction === 'down' ? '↓' : '•';
+
+ const statusClass = lift.disabled ? 'status-malfunction' :
+ lift.isMoving ? 'status-moving' :
+ lift.doorOpen ? 'status-door-open' : 'status-idle';
+
+ const displayFloor = this.floors - lift.currentFloor - 1;
+ const floorIndicator = displayFloor === 0 ? 'G' : displayFloor;
+ // Get lift color
+ const liftColor = this.getLiftColor(lift.id);
+
+ card.innerHTML = `
+
+ LIFT ${lift.id}
+ ${status}
+
+
+
📍
+
Floor: ${floorIndicator}
+
+
+
${directionIcon}
+
Direction: ${lift.direction || 'Stationary'}
+
+
+
🔄
+
Queue: ${targetFloorsText}
+
+ ${lift.disabled ? '⚠️ OUT OF SERVICE
' : ''}
+ `;
+
+ // Apply card color accent
+ card.style.borderLeftColor = liftColor.bg.split(',')[1];
+
+ container.appendChild(card);
+ });
+ }
+
+ showStatus(message, type) {
+ const statusElement = document.getElementById('malfunction-status');
+ statusElement.textContent = message;
+ statusElement.className = `status-display ${type}`;
+
+ // Clear status after 5 seconds
+ setTimeout(() => {
+ statusElement.textContent = '';
+ statusElement.className = 'status-display';
+ }, 5000);
+ }
+
+ delay(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+
+ // Get a unique color for each lift based on its ID
+ getLiftColor(liftId) {
+ const colors = [
+ { bg: 'linear-gradient(45deg, #f39c12, #e74c3c)', door: 'linear-gradient(90deg, #f39c12, #e74c3c)' }, // Orange-Red
+ { bg: 'linear-gradient(45deg, #3498db, #2980b9)', door: 'linear-gradient(90deg, #3498db, #2980b9)' }, // Blue
+ { bg: 'linear-gradient(45deg, #2ecc71, #27ae60)', door: 'linear-gradient(90deg, #2ecc71, #27ae60)' }, // Green
+ { bg: 'linear-gradient(45deg, #9b59b6, #8e44ad)', door: 'linear-gradient(90deg, #9b59b6, #8e44ad)' }, // Purple
+ { bg: 'linear-gradient(45deg, #1abc9c, #16a085)', door: 'linear-gradient(90deg, #1abc9c, #16a085)' }, // Teal
+ ];
+
+ // Get color based on lift ID (1-indexed)
+ const colorIndex = (liftId - 1) % colors.length;
+ return colors[colorIndex];
+ }
+}
+
+// Initialize the simulation when the page loads
+document.addEventListener('DOMContentLoaded', () => {
+ new LiftSimulation();
+});
+
+// Additional utility functions for enhanced functionality
+class LiftUtils {
+ static calculateOptimalPath(lifts, requests) {
+ // Advanced algorithm for optimal lift dispatching
+ // This could be enhanced with more sophisticated algorithms
+ return requests;
+ }
+
+ static validateFloorRange(floor, maxFloors) {
+ return floor >= 0 && floor < maxFloors;
+ }
+
+ static formatFloorDisplay(floor) {
+ return floor === 0 ? 'Ground Floor' : `Floor ${floor}`;
+ }
+}
\ No newline at end of file