From 01068b120a0d91e84f7d7e9feafa1ca6a421edc1 Mon Sep 17 00:00:00 2001 From: sabazadam Date: Sun, 21 Dec 2025 01:03:40 +0300 Subject: [PATCH] Rescheduling button added the calender is updated with new version --- .../ScheduleCalendarController.java | 65 ++++++++++++- .../service/ScheduleGeneratorService.java | 97 +++++++++++++++++++ .../se302/view/schedule-calendar-view.fxml | 2 + 3 files changed, 162 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/se302/controller/ScheduleCalendarController.java b/src/main/java/org/example/se302/controller/ScheduleCalendarController.java index f4dced4..8ad1c81 100644 --- a/src/main/java/org/example/se302/controller/ScheduleCalendarController.java +++ b/src/main/java/org/example/se302/controller/ScheduleCalendarController.java @@ -49,6 +49,8 @@ public class ScheduleCalendarController { @FXML private Button generateButton; @FXML + private Button regenerateButton; + @FXML private Button cancelButton; @FXML private Label statusLabel; @@ -251,24 +253,81 @@ public class ScheduleCalendarController { } private void startGeneration(ScheduleConfiguration config) { + startGeneration(config, false); + } + + /** + * Regenerate the schedule with randomization to produce a different result. + */ + @FXML + private void onRegenerateSchedule() { + // Validate data + if (dataManager.getTotalCourses() == 0) { + showAlert(Alert.AlertType.WARNING, "No Data", + "Please import course data before generating a schedule."); + return; + } + + if (dataManager.getTotalClassrooms() == 0) { + showAlert(Alert.AlertType.WARNING, "No Classrooms", + "Please import classroom data before generating a schedule."); + return; + } + + // Validate configuration + if (startDatePicker.getValue() == null) { + showAlert(Alert.AlertType.WARNING, "Missing Date", + "Please select a start date for the exam period."); + return; + } + + // Build configuration + ScheduleConfiguration config = buildConfiguration(); + + // Validate total slots + int totalSlots = config.getTotalSlots(); + int totalCourses = dataManager.getTotalCourses(); + int totalClassrooms = dataManager.getTotalClassrooms(); + + if (totalSlots * totalClassrooms < totalCourses) { + showAlert(Alert.AlertType.WARNING, "Insufficient Capacity", + String.format( + "Not enough time slots! You have %d courses but only %d total capacity (%d slots × %d classrooms).\n\nPlease increase days or slots per day.", + totalCourses, totalSlots * totalClassrooms, totalSlots, totalClassrooms)); + return; + } + + // Start generation with randomization enabled + startGeneration(config, true); + } + + private void startGeneration(ScheduleConfiguration config, boolean useRandomization) { // Update UI for generation generateButton.setDisable(true); + if (regenerateButton != null) + regenerateButton.setDisable(true); cancelButton.setDisable(false); progressContainer.setVisible(true); progressContainer.setManaged(true); progressBar.setProgress(0); if (progressIndicator != null) progressIndicator.setProgress(-1.0); // Indeterminate - progressLabel.setText("Generating Schedule..."); + progressLabel.setText(useRandomization ? "Regenerating Schedule..." : "Generating Schedule..."); if (progressDetailLabel != null) progressDetailLabel.setText("Initializing CSP solver..."); - statusLabel.setText("⏳ Generating schedule..."); + statusLabel.setText("⏳ " + (useRandomization ? "Regenerating" : "Generating") + " schedule..."); statusLabel.setStyle("-fx-text-fill: #3498db;"); long startTime = System.currentTimeMillis(); // Create generator service generatorService = new ScheduleGeneratorService(); + + // Enable randomization if requested (for regenerate button) + if (useRandomization) { + generatorService.enableRandomization(); + } + generatorService.setProgressListener((progress, message) -> { Platform.runLater(() -> { progressBar.setProgress(progress); @@ -298,6 +357,8 @@ public class ScheduleCalendarController { ScheduleConfiguration config, long durationMs) { // Reset UI generateButton.setDisable(false); + if (regenerateButton != null) + regenerateButton.setDisable(false); cancelButton.setDisable(true); progressContainer.setVisible(false); progressContainer.setManaged(false); diff --git a/src/main/java/org/example/se302/service/ScheduleGeneratorService.java b/src/main/java/org/example/se302/service/ScheduleGeneratorService.java index 1d9e9e5..8fb3368 100644 --- a/src/main/java/org/example/se302/service/ScheduleGeneratorService.java +++ b/src/main/java/org/example/se302/service/ScheduleGeneratorService.java @@ -25,6 +25,8 @@ public class ScheduleGeneratorService { private final DataManager dataManager; private final AtomicBoolean cancelled; private ProgressListener progressListener; + private Random random; + private boolean useRandomization = false; // Timeout in milliseconds (30 seconds) private static final long TIMEOUT_MS = 30000; @@ -38,6 +40,31 @@ public class ScheduleGeneratorService { public ScheduleGeneratorService() { this.dataManager = DataManager.getInstance(); this.cancelled = new AtomicBoolean(false); + this.random = new Random(); + } + + /** + * Enable randomization with a new random seed. + * Call this before generateSchedule() to get a different schedule each time. + */ + public void enableRandomization() { + this.useRandomization = true; + this.random = new Random(); // New seed each time + } + + /** + * Enable randomization with a specific seed (useful for reproducibility). + */ + public void enableRandomization(long seed) { + this.useRandomization = true; + this.random = new Random(seed); + } + + /** + * Disable randomization (use deterministic algorithm). + */ + public void disableRandomization() { + this.useRandomization = false; } /** @@ -415,6 +442,7 @@ public class ScheduleGeneratorService { /** * Selects the best assignment candidate based on the optimization strategy. + * When randomization is enabled, selects randomly from top-scoring candidates. */ private AssignmentCandidate selectBestCandidate( List candidates, @@ -423,6 +451,11 @@ public class ScheduleGeneratorService { Set enrolledStudents, Map> slotClassrooms) { + // If randomization is enabled, shuffle candidates first to introduce variety + if (useRandomization) { + Collections.shuffle(candidates, random); + } + ScheduleConfiguration.OptimizationStrategy strategy = config.getOptimizationStrategy(); // Handle default/null strategy @@ -461,10 +494,74 @@ public class ScheduleGeneratorService { break; } + // When randomization is enabled, pick randomly from top candidates with similar + // scores + if (useRandomization && candidates.size() > 1) { + return selectFromTopCandidates(candidates, studentExams, enrolledStudents, strategy, slotClassrooms); + } + // Return best (first) return candidates.get(0); } + /** + * Selects randomly from candidates with similar (near-optimal) scores. + * This introduces variety while still respecting the optimization strategy. + */ + private AssignmentCandidate selectFromTopCandidates( + List candidates, + Map> studentExams, + Set enrolledStudents, + ScheduleConfiguration.OptimizationStrategy strategy, + Map> slotClassrooms) { + + // Calculate the score of the best candidate + double bestScore = calculateCandidateScore(candidates.get(0), studentExams, enrolledStudents, strategy, + slotClassrooms); + + // Tolerance for considering candidates "equally good" (within 10% of best) + double tolerance = Math.abs(bestScore) * 0.1; + if (tolerance < 1.0) + tolerance = 1.0; // Minimum tolerance + + // Collect all candidates within tolerance of the best score + List topCandidates = new ArrayList<>(); + for (AssignmentCandidate candidate : candidates) { + double score = calculateCandidateScore(candidate, studentExams, enrolledStudents, strategy, slotClassrooms); + if (Math.abs(score - bestScore) <= tolerance) { + topCandidates.add(candidate); + } + } + + // Pick randomly from top candidates + if (topCandidates.isEmpty()) { + return candidates.get(0); + } + return topCandidates.get(random.nextInt(topCandidates.size())); + } + + /** + * Calculate a score for a candidate based on the optimization strategy. + */ + private double calculateCandidateScore( + AssignmentCandidate candidate, + Map> studentExams, + Set enrolledStudents, + ScheduleConfiguration.OptimizationStrategy strategy, + Map> slotClassrooms) { + + switch (strategy) { + case MINIMIZE_DAYS: + return candidate.day * 100.0 + candidate.slot; + case MINIMIZE_CLASSROOMS: + String key = candidate.day + "-" + candidate.slot; + return slotClassrooms.getOrDefault(key, Collections.emptySet()).size() * 100.0 + candidate.day; + case STUDENT_FRIENDLY: + default: + return calculateStudentFriendlyPenalty(candidate, studentExams, enrolledStudents); + } + } + /** * Calculates penalty for student-friendly reference. * Higher penalty = worse for students. diff --git a/src/main/resources/org/example/se302/view/schedule-calendar-view.fxml b/src/main/resources/org/example/se302/view/schedule-calendar-view.fxml index 6492a40..7d396cf 100644 --- a/src/main/resources/org/example/se302/view/schedule-calendar-view.fxml +++ b/src/main/resources/org/example/se302/view/schedule-calendar-view.fxml @@ -90,6 +90,8 @@