diff --git a/MainWindow.axaml b/MainWindow.axaml index 6b18d6b..2ca5aa2 100644 --- a/MainWindow.axaml +++ b/MainWindow.axaml @@ -20,7 +20,7 @@ - + @@ -100,22 +100,112 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + diff --git a/MainWindow.axaml.cs b/MainWindow.axaml.cs index c6bb26a..c0c2bf8 100644 --- a/MainWindow.axaml.cs +++ b/MainWindow.axaml.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using Avalonia.Controls; +using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Platform.Storage; @@ -119,6 +120,30 @@ public partial class MainWindow : Window private void BtnCraftCourses_OnClick(object? sender, RoutedEventArgs e) { // Craft courses here / call course-crafter + CourseCrafter.Craft(); + RefreshResultView(); + TbiResults.Focus(); + } + + private void RefreshResultView() + { + LbResult.Items.Clear(); + foreach (Student s in Settings.Instance.Students) + { + try + { + for(int i = 0; i Students = new(); + } + public static void Craft() + { + var settings = Settings.Instance; + var students = settings.Students; + var sports = settings.Sports; + + if (students.Count == 0 || sports.Count == 0) + return; + + int semesterCount = sports.Max(s => s.Semester.Length); + + // Result initialisieren + foreach (var st in students) + st.Result = Enumerable.Repeat("Fehler", semesterCount).ToList(); + + // Mapping Name + Alternativen → Sport + var nameMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var sp in sports) + { + nameMap[sp.Name] = sp; + foreach (var alt in sp.AlternativeNames) + nameMap[alt] = sp; + } + + // ===== Semesterweise Planung ===== + for (int sem = 0; sem < semesterCount; sem++) + { + int remainingCourseSlots = settings.NumCoursesPerSemester; + + // Interessenten dieses Semesters + var interested = new Dictionary>(); + foreach (var sp in sports) + interested[sp] = new List(); + + foreach (var st in students) + { + if (st.SelectedCourseNames.Count <= sem) + continue; + + var sel = st.SelectedCourseNames[sem]; + if (nameMap.TryGetValue(sel, out var sp)) + interested[sp].Add(st); + } + + var instances = new List(); + + // ===== Kurse erzeugen ===== + foreach (var sp in sports) + { + if (sem >= sp.Semester.Length) + continue; + + int demand = interested[sp].Count; + if (demand < sp.MinStudents) + continue; + + int needed = (int)Math.Ceiling(demand / (double)sp.MaxStudents); + int allowed = Math.Min(needed, sp.Semester[sem]); + allowed = Math.Min(allowed, remainingCourseSlots); + + if (allowed <= 0) + continue; + + remainingCourseSlots -= allowed; + + for (int i = 0; i < allowed; i++) + { + instances.Add(new CourseInstance + { + Sport = sp, + Remaining = sp.MaxStudents + }); + } + } + + // ===== Schüler verteilen ===== + foreach (var sp in sports) + { + var pool = interested[sp]; + if (pool.Count == 0) + continue; + + var targets = instances.Where(i => i.Sport == sp).ToList(); + if (targets.Count == 0) + continue; + + int idx = 0; + foreach (var st in pool) + { + if (st.SelectedCourseNames.Count <= sem) + continue; + + if (st.Result![sem] != "Fehler") + continue; + + bool assigned = false; + + for (int t = 0; t < targets.Count; t++) + { + var inst = targets[(idx + t) % targets.Count]; + if (inst.Remaining > 0) + { + inst.Remaining--; + inst.Students.Add(st); + st.Result[sem] = sp.Name; + idx++; + assigned = true; + break; + } + } + + if (!assigned) + continue; + } + } + + // ===== Alternativen ===== + foreach (var st in students) + { + if (st.SelectedCourseNames.Count <= sem) + continue; + + if (st.Result![sem] != "Fehler") + continue; + + var sel = st.SelectedCourseNames[sem]; + if (!nameMap.TryGetValue(sel, out var wanted)) + continue; + + foreach (var altId in wanted.AlternativeCourses) + { + var altSport = sports.FirstOrDefault(s => s.ID == altId); + if (altSport == null) + continue; + + var inst = instances.FirstOrDefault(i => i.Sport == altSport && i.Remaining > 0); + if (inst == null) + continue; + + inst.Remaining--; + inst.Students.Add(st); + st.Result[sem] = altSport.Name; + break; + } + } + } + } + + public static string GenerateStatistics() + { + var settings = Settings.Instance; + var students = settings.Students; + var sports = settings.Sports; + + if (students.Count == 0 || sports.Count == 0) + return "Keine Daten vorhanden."; + + int semesterCount = sports.Max(s => s.Semester.Length); + + // Kurszählung: (Semester, SportName) → Liste Schüler + var dict = new Dictionary<(int, string), List>(); + + foreach (var st in students) + { + if (st.Result == null) + continue; + + for (int sem = 0; sem < st.Result.Count; sem++) + { + var course = st.Result[sem]; + if (string.IsNullOrWhiteSpace(course) || course == "Fehler") + continue; + + var key = (sem, course); + + if (!dict.ContainsKey(key)) + dict[key] = new List(); + + dict[key].Add(st); + } + } + + var sb = new System.Text.StringBuilder(); + + sb.AppendLine($"Anzahl generierter Kurse: {dict.Count}"); + sb.AppendLine("Übersicht:"); + + foreach (var entry in dict.OrderBy(e => e.Key.Item1).ThenBy(e => e.Key.Item2)) + { + int semester = entry.Key.Item1 + 1; + string name = entry.Key.Item2; + int count = entry.Value.Count; + + sb.AppendLine($"Semester {semester}: {name} — {count} Schüler*innen"); + } + + return sb.ToString(); + } +} diff --git a/structs.cs b/structs.cs index 8bca1a5..9b492ba 100644 --- a/structs.cs +++ b/structs.cs @@ -1,32 +1,56 @@ using System.Collections.Generic; +using Avalonia.Data; namespace spplus; public class Sport { + public int ID { get; set; } = 0; public string Name { get; set; } = "Neuer Kurs"; // Kursname - public int MaxCoursesPerSemester { get; set; } = 1; // Maximale Anzahl an Kursen pro Semester - public int MaxStudents { get; set; } = 20; // Maximale Anzahl an Schülern pro Kurs + + public int MaxStudents { get; set; } = 25; // Maximale Anzahl an Schülern pro Kurs public int MinStudents { get; set; } = 5; // Minimale Anzahl an Schülern pro Kurs - public int[] Semester { get; set; } = [1, 2, 3, 4]; // Angebot in diesen Semestern + public int[] Semester { get; set; } = [2, 2, 2, 2]; // Maximale Anzahl an Angeboten in den jeweiligen Index-Semestern (0 => 1. Semester) + public List AlternativeNames { get; set; } = new(); + public List AlternativeCourses { get; set; } = new(); protected Sport() { } - protected Sport(string name) + public Sport(string name) { Name = name; } - protected Sport(string name, int maxCoursesPerSemester, int maxStudents, int minStudents, int[] semester) + public Sport(string name, int maxCoursesPerSemester, int maxStudents, int minStudents, int[] semester, List alternativeNames) { Name = name; - MaxCoursesPerSemester = maxCoursesPerSemester; MaxStudents = maxStudents; MinStudents = minStudents; Semester = semester; + AlternativeNames = alternativeNames; + } + + public void AddAlternativeSport(int id) + { + AlternativeCourses.Add(id); + } + + public void ClearAlternativeSport() + { + AlternativeCourses.Clear(); + } + + public override string ToString() + { + var alt = ""; + foreach (var s in AlternativeNames) + { + alt += s + ", "; + } + return $"{Name} ({alt})"; } } @@ -34,7 +58,6 @@ public class Student { public string ID { get; set; } = ""; // ID des Schüler (z.B. NolteSeb) public string Name { get; set; } = ""; // Name des Schülers - public Sport[] SelectedCourses { get; set; } = new Sport[4]; // Kurswahl public List SelectedCourseNames { get; set; } = new(); public List? Result { get; set; } = null; @@ -62,6 +85,7 @@ public class Settings public List Students { get; set; } = []; public List Sports { get; set; } = []; + public int NumCoursesPerSemester { get; set; } = 10; public Settings() { @@ -72,4 +96,34 @@ public class Settings { // Hier importieren... } + + public static void ImportInitial() + { + Instance.Sports.Add(new Sport("Tischtennis"){ AlternativeNames = {"Sport_TT"}}); + Instance.Sports.Add(new Sport("Badminton"){ AlternativeNames = {"Sport_BM"}}); + Instance.Sports.Add(new Sport("Gymnastik/Tanz"){ AlternativeNames = {"Sport_Gym"}}); + Instance.Sports.Add(new Sport("Schwimmen"){ AlternativeNames = {"Sport_SW"}, Semester = [1, 1, 1, 1], MaxStudents = 18}); + Instance.Sports.Add(new Sport("Bouldern"){ AlternativeNames = {"Sport_BO"}, Semester = [1, 1, 1, 1]}); + Instance.Sports.Add(new Sport("Basketball"){ AlternativeNames = {"Sport_BS"}}); + Instance.Sports.Add(new Sport("Fitness"){ AlternativeNames = {"Sport_Fit"}}); + Instance.Sports.Add(new Sport("Fußball"){ AlternativeNames = {"Sport_Fuß"}, Semester = [1, 0, 1, 0]}); + Instance.Sports.Add(new Sport("Handball"){ AlternativeNames = {"Sport_HB"}}); + Instance.Sports.Add(new Sport("Leichtathletik"){ AlternativeNames = {"Sport_LA"}, Semester = [1, 0, 1, 0], MaxStudents = 18}); + Instance.Sports.Add(new Sport("Tennis"){ AlternativeNames = {"Sport_Te"}}); + Instance.Sports.Add(new Sport("Turnen"){ AlternativeNames = {"Sport_Tur"}}); + Instance.Sports.Add(new Sport("Volleyball"){ AlternativeNames = {"Sport_VB"}}); + } + + public static string? GetSportNameFromID(int id) + { + foreach (var s in Instance.Sports) + { + if (s.ID == id) + { + return s.Name; + } + } + + return null; + } } \ No newline at end of file