Scheduling algorithm avoided students that have many lectures and leading a bug, its fixed. Also scheduling calendar had scrolling issue that fixed too (Commit error. now i sending the codes)

This commit is contained in:
sabazadam
2025-12-19 21:21:46 +03:00
parent 8c7cb9e0fb
commit 96fc9408ec
6 changed files with 54 additions and 29 deletions

View File

@@ -121,7 +121,7 @@ public class ScheduleCalendarController {
private void initializeSpinners() { private void initializeSpinners() {
// Number of days spinner (1-30) // Number of days spinner (1-30)
SpinnerValueFactory<Integer> daysFactory = new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 30, 5); SpinnerValueFactory<Integer> daysFactory = new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 30, 8);
numDaysSpinner.setValueFactory(daysFactory); numDaysSpinner.setValueFactory(daysFactory);
// Slots per day spinner (1-10) // Slots per day spinner (1-10)
@@ -362,6 +362,10 @@ public class ScheduleCalendarController {
} }
private void updateStatistics(ScheduleState schedule, long durationMs) { private void updateStatistics(ScheduleState schedule, long durationMs) {
// Update total courses (BUG FIX: this was missing!)
totalCoursesLabel.setText(String.valueOf(schedule.getTotalCourses()));
// Update scheduled courses
scheduledCoursesLabel.setText(String.valueOf(schedule.getAssignedCourses())); scheduledCoursesLabel.setText(String.valueOf(schedule.getAssignedCourses()));
// Count unique classrooms used // Count unique classrooms used
@@ -390,6 +394,12 @@ public class ScheduleCalendarController {
int numDays = config.getNumDays(); int numDays = config.getNumDays();
int slotsPerDay = config.getSlotsPerDay(); int slotsPerDay = config.getSlotsPerDay();
// Dynamically set minimum grid width to fit all columns
// Time column (120px) + each day column (200px) + padding
double minGridWidth = 120 + (numDays * 200) + 40;
scheduleGrid.setMinWidth(minGridWidth);
scheduleGrid.setPrefWidth(minGridWidth);
// Column constraints - make columns wider for better visibility // Column constraints - make columns wider for better visibility
ColumnConstraints headerCol = new ColumnConstraints(); ColumnConstraints headerCol = new ColumnConstraints();
headerCol.setMinWidth(100); headerCol.setMinWidth(100);
@@ -398,9 +408,9 @@ public class ScheduleCalendarController {
for (int day = 0; day < numDays; day++) { for (int day = 0; day < numDays; day++) {
ColumnConstraints dayCol = new ColumnConstraints(); ColumnConstraints dayCol = new ColumnConstraints();
dayCol.setMinWidth(180); dayCol.setMinWidth(135);
dayCol.setPrefWidth(220); dayCol.setPrefWidth(150);
dayCol.setHgrow(Priority.ALWAYS); dayCol.setHgrow(Priority.NEVER); // Prevent shrinking, allow horizontal scroll
scheduleGrid.getColumnConstraints().add(dayCol); scheduleGrid.getColumnConstraints().add(dayCol);
} }
@@ -531,9 +541,10 @@ public class ScheduleCalendarController {
examBox.setOnDragDone(e -> { examBox.setOnDragDone(e -> {
draggedExam = null; draggedExam = null;
examBox.setOpacity(1.0); examBox.setOpacity(1.0);
// Refresh grid to reset all cell colors // Defer grid refresh to after drag event completes to avoid crash
// when modifying scene graph during active event processing
if (currentSchedule != null && currentConfig != null) { if (currentSchedule != null && currentConfig != null) {
displayScheduleGrid(currentSchedule, currentConfig); Platform.runLater(() -> displayScheduleGrid(currentSchedule, currentConfig));
} }
e.consume(); e.consume();
}); });

View File

@@ -82,7 +82,7 @@ public class ScheduleConfiguration {
* Creates a default schedule configuration. * Creates a default schedule configuration.
*/ */
public ScheduleConfiguration() { public ScheduleConfiguration() {
this.numDays = 5; this.numDays = 8;
this.slotsPerDay = 4; this.slotsPerDay = 4;
this.startDate = LocalDate.now().plusDays(7); // Default: start in a week this.startDate = LocalDate.now().plusDays(7); // Default: start in a week
this.slotDurationMinutes = 120; // 2 hours this.slotDurationMinutes = 120; // 2 hours

View File

@@ -63,28 +63,36 @@ public class ConstraintValidator {
ValidationResult doubleBookingResult = checkNoDoubleBooking(assignment, scheduleState); ValidationResult doubleBookingResult = checkNoDoubleBooking(assignment, scheduleState);
result.merge(doubleBookingResult); result.merge(doubleBookingResult);
// Check student constraints - only check same time slot conflicts (always // Check student constraints
// required)
Course course = dataManager.getCourse(assignment.getCourseCode()); Course course = dataManager.getCourse(assignment.getCourseCode());
if (course != null) { if (course != null) {
// Check if student has another exam at the SAME time slot (hard constraint) // HARD CONSTRAINT: Check if student has another exam at the SAME time slot
// This is ALWAYS required
ValidationResult sameTimeResult = checkNoSameTimeExams(assignment, scheduleState, course); ValidationResult sameTimeResult = checkNoSameTimeExams(assignment, scheduleState, course);
result.merge(sameTimeResult); result.merge(sameTimeResult);
// Only check consecutive and max per day if back-to-back is NOT allowed // HARD CONSTRAINT: Max 2 exams per day per student
if (!allowBackToBack) { // This is ALWAYS enforced, regardless of allowBackToBack flag
for (String studentId : course.getEnrolledStudents()) { for (String studentId : course.getEnrolledStudents()) {
// Check no consecutive exams (soft - skip if allowBackToBack)
ValidationResult consecutiveResult = checkNoConsecutiveExams(
studentId, assignment, scheduleState);
result.merge(consecutiveResult);
// Check max 2 exams per day
ValidationResult maxPerDayResult = checkMaxTwoExamsPerDay( ValidationResult maxPerDayResult = checkMaxTwoExamsPerDay(
studentId, assignment, scheduleState); studentId, assignment, scheduleState);
result.merge(maxPerDayResult); result.merge(maxPerDayResult);
// If already invalid, no need to check more students // If max per day violated, no need to check more students
if (!result.isValid()) {
break;
}
}
// SOFT CONSTRAINT: No consecutive exams (back-to-back)
// Only enforced when allowBackToBack = false
if (!allowBackToBack) {
for (String studentId : course.getEnrolledStudents()) {
ValidationResult consecutiveResult = checkNoConsecutiveExams(
studentId, assignment, scheduleState);
result.merge(consecutiveResult);
// If consecutive violation, no need to check more students
if (!result.isValid()) { if (!result.isValid()) {
break; break;
} }

View File

@@ -170,10 +170,13 @@ public class ScheduleGeneratorService {
// Try each classroom // Try each classroom
for (Classroom classroom : suitableClassrooms) { for (Classroom classroom : suitableClassrooms) {
// Temporarily assign // Temporarily assign (using updateAssignment to maintain assignedCourses counter)
assignment.setDay(timeSlot.day); scheduleState.updateAssignment(
assignment.setTimeSlotIndex(timeSlot.slot); assignment.getCourseCode(),
assignment.setClassroomId(classroom.getClassroomId()); timeSlot.day,
timeSlot.slot,
classroom.getClassroomId()
);
// Validate assignment - pass allowBackToBack from config // Validate assignment - pass allowBackToBack from config
ConstraintValidator.ValidationResult validationResult = validator.validateAssignment(assignment, ConstraintValidator.ValidationResult validationResult = validator.validateAssignment(assignment,
@@ -186,10 +189,13 @@ public class ScheduleGeneratorService {
} }
} }
// Backtrack: reset assignment // Backtrack: reset assignment (using updateAssignment to maintain assignedCourses counter)
assignment.setDay(-1); scheduleState.updateAssignment(
assignment.setTimeSlotIndex(-1); assignment.getCourseCode(),
assignment.setClassroomId(null); -1,
-1,
null
);
} }
} }

View File

@@ -101,9 +101,9 @@
<VBox spacing="12" VBox.vgrow="ALWAYS" styleClass="card"> <VBox spacing="12" VBox.vgrow="ALWAYS" styleClass="card">
<Label text="Import Messages" styleClass="subsection-title"/> <Label text="Import Messages" styleClass="subsection-title"/>
<TextArea fx:id="messagesArea" editable="false" wrapText="true" VBox.vgrow="ALWAYS" <TextArea fx:id="messagesArea" editable="false" wrapText="true" VBox.vgrow="ALWAYS"
prefHeight="200" prefHeight="300" minHeight="250" maxHeight="500"
promptText="Validation messages and import results will appear here..." promptText="Validation messages and import results will appear here..."
style="-fx-font-family: 'Consolas', 'Monaco', monospace; -fx-font-size: 12px;"/> style="-fx-font-family: 'Consolas', 'Monaco', monospace; -fx-font-size: 13px; -fx-padding: 12px;"/>
</VBox> </VBox>
<!-- Action Buttons --> <!-- Action Buttons -->

View File

@@ -79,7 +79,7 @@
<!-- Summary --> <!-- Summary -->
<HBox spacing="12" alignment="CENTER_LEFT" styleClass="summary-row"> <HBox spacing="12" alignment="CENTER_LEFT" styleClass="summary-row">
<Label text="📊 Total Time Slots:" style="-fx-font-weight: 700;"/> <Label text="📊 Total Time Slots:" style="-fx-font-weight: 700;"/>
<Label fx:id="summaryLabel" text="5 days × 4 slots = 20 slots" <Label fx:id="summaryLabel" text="8 days × 4 slots = 32 slots"
styleClass="summary-value"/> styleClass="summary-value"/>
</HBox> </HBox>
</VBox> </VBox>