using System; using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine; namespace Parking { public class ParkingManager : MonoBehaviour { public const float Width = 68; public const float Height = 29; public static ParkingManager Instance; private static readonly TimeSpan StartTime = TimeSpan.FromHours(5) + TimeSpan.FromMinutes(45); private static readonly TimeSpan EndTime = TimeSpan.FromHours(17) + TimeSpan.FromMinutes(15); private static TimeSpan _currentTime = StartTime; [SerializeField] private int stepTime = 15; [SerializeField] private TextMeshProUGUI timeText; [SerializeField] public TextMeshProUGUI countsText; [SerializeField] public TextMeshProUGUI rejectedText; [SerializeField] public GameObject spotPrefabA; [SerializeField] public GameObject spotPrefabB; [SerializeField] public GameObject spotPrefabC; [SerializeField] public GameObject spotPrefabD; [SerializeField] public GameObject carPrefab; [SerializeField] public Transform mainPlanContainer; [SerializeField] public Transform emergencyPlanContainer; private readonly float[] _spotHeights = {4f, 4.5f, 5f, 7.5f}; private readonly List> _spotMap = new() {new List(), new List(), new List(), new List()}; private bool _emergencyActivated; private int _initialConfigurationSpotCount; private int[] _rejectedDrivers = {0, 0, 0, 0}; private void Awake() { Instance = this; } private void Start() { timeText.text = _currentTime.ToString(); rejectedText.text = $"Małe: {_rejectedDrivers[0]} Średnie: {_rejectedDrivers[1]} " + $"Duże: {_rejectedDrivers[2]}"; GenerateEmergencyPlan(); DataImporter.ReadFile("Assets/Data/Tablica4.csv"); InitialConfigurationGenerator generator = new(); ArrangeSpots(generator.FindSolution()); ReserveInitialSpots(); } public void Emergency() { ResetDrivers(); _emergencyActivated = true; } private void ReserveInitialSpots() { for (int j = 3; j >= 0; j--) foreach (Spot spot in _spotMap[j]) { spot.Reserved = false; spot.ReservedPriority = 0; } for (int i = 0; i < Math.Min(DataImporter.Drivers.Count, _initialConfigurationSpotCount); i++) { bool found = false; // Find spot for (int j = 3; j >= 0 && !found; j--) foreach (Spot spot in _spotMap[j]) if (spot.Size == DataImporter.Drivers[i].Size && !spot.Reserved) { spot.Reserved = true; spot.ReservedPriority = DataImporter.Drivers[i].Priority; found = true; break; } } // Find spot // for (var j = 3; j >= 0; j--) // foreach (Spot spot in _spotMap[j]) // if (!spot.Reserved && spot.Size != Size.D) // Debug.Log("Spot not reserved"); } public void AdvanceTime() { // Update time _currentTime += TimeSpan.FromMinutes(stepTime); if (_currentTime > EndTime) { _currentTime = StartTime; timeText.text = _currentTime.ToString(); _rejectedDrivers = new[] {0, 0, 0, 0}; rejectedText.text = $"Małe: {_rejectedDrivers[0]} Średnie: {_rejectedDrivers[1]} " + $"Duże: {_rejectedDrivers[2]}"; if (_emergencyActivated) { emergencyPlanContainer.gameObject.SetActive(true); mainPlanContainer.gameObject.SetActive(false); } else { ReserveInitialSpots(); ResetDrivers(); } return; } timeText.text = _currentTime.ToString(); if (_emergencyActivated) return; foreach (Driver driver in DataImporter.Drivers) { // TODO: Check if car can stay before other reservation - may not work for priority-based reservations bool triesToPark = _currentTime <= driver.Times[3].TimeOfDay && _currentTime > driver.Times[2].TimeOfDay; bool leftTheParking = _currentTime > driver.Times[3].TimeOfDay && driver.Parked; bool reservedButTimedout = (_currentTime - driver.Times[0].TimeOfDay).TotalMinutes > driver.UpdateInterval && GetReservedSpotCount(driver.Priority, driver.Size) > 0 && !driver.Parked && !driver.Rejected && _currentTime <= driver.Times[3].TimeOfDay; if (triesToPark && !driver.Parked && !driver.Rejected) { if (!PlaceCarOnParking(driver)) { Debug.Log($"Placing failed for car {driver.Number}"); driver.Rejected = true; _rejectedDrivers[(int) driver.Size]++; rejectedText.text = $"Małe: {_rejectedDrivers[0]} Średnie: {_rejectedDrivers[1]} " + $"Duże: {_rejectedDrivers[2]}"; } } else if (leftTheParking) { driver.Spot.Reserved = false; driver.Reset(); } else if (reservedButTimedout) { Debug.Log($"Reserved but timed out driver {driver.Number}"); driver.Rejected = true; } } ReconfigureSpots(); } private void ResetDrivers() { foreach (Driver driver in DataImporter.Drivers) { if (driver.Spot != null) { driver.Spot.Reserved = false; driver.Spot.ReservedPriority = 0; } driver.Reset(); } } private int GetReservedSpotCount(int priority, Size size) { int count = 0; foreach (var list in _spotMap) foreach (Spot spot in list) if (spot.Size == size && spot.Reserved && spot.ReservedPriority == priority) count++; return count; } private bool FindReservedSpot(Driver driver, out Spot spot) { foreach (var list in _spotMap) foreach (Spot spot1 in list) if (spot1.Reserved && spot1.ReservedPriority == driver.Priority) { spot = spot1; return true; } spot = new Spot(); return false; } private bool PlaceCarOnParking(Driver driver) { if (FindReservedSpot(driver, out Spot reservedSpot)) { if (reservedSpot.Free) { PlaceDriverOnSpot(driver, reservedSpot); return true; } Debug.Log("Spot should be reserved but is taken"); } foreach (var t in _spotMap) foreach (Spot spot in t) { bool spotAvailable = spot.Size == driver.Size && spot.Free && (spot.ParkingDirection == driver.ParkingPreference || spot.ParkingDirection == ParkingPreference.Any) && !spot.Reserved; if (spotAvailable) { PlaceDriverOnSpot(driver, spot); return true; } } foreach (var t in _spotMap) foreach (Spot spot in t) if (spot.Size == driver.Size && spot.Free && !spot.Reserved) { PlaceDriverOnSpot(driver, spot); return true; } return false; } private void PlaceDriverOnSpot(Driver driver, Spot spot) { spot.Free = false; spot.Reserved = false; driver.Spot = spot; driver.Parked = true; driver.GameObject = Instantiate(carPrefab, spot.GameObject.transform, true); driver.GameObject.GetComponentInChildren().text = driver.Number.ToString(); driver.GameObject.GetComponentInChildren().transform.rotation = Quaternion.Euler(new Vector3(0, 0, spot.Flipped ? 180 : 0)); driver.GameObject.transform.position = spot.GameObject.transform.position; driver.GameObject.transform.localPosition += new Vector3(0, 0, -1); driver.GameObject.transform.rotation = Quaternion.Euler(new Vector3(0, 0, spot.Flipped ? 180 : 0)); } public void UpdateText(string text) { countsText.text = text; } private void ReconfigureSpots() { List prefabs = new List() {spotPrefabA, spotPrefabB, spotPrefabC}; int[] freeSpots = GetFreeSpotCount(); var nextCars = GetNextCars(10); int[] plannedSpots = {0, 0, 0, 0}; foreach (Driver driver in nextCars) plannedSpots[(int) driver.Size]++; int[] neededSpots = {0, 0, 0, 0}; for (int i = 0; i < neededSpots.Length; i++) neededSpots[i] = Math.Max(plannedSpots[i] - freeSpots[i], 0); if (neededSpots.Sum() > 5) { Debug.Log($"Needed spots = {neededSpots[0]} {neededSpots[1]} {neededSpots[2]} {neededSpots[3]}"); Debug.Log("Attempting reconfiguration..."); for (int size = 0; size < 3; size++) { bool foundReplacement = true; while (foundReplacement && neededSpots[size] != 0) { foundReplacement = false; foreach (var t in _spotMap) { foreach (Spot spot in t) { if ((int) spot.Size > size && spot.Free && spot.Lane != 3 && spot.Size != Size.D) { foundReplacement = true; float diff = (_spotHeights[(int)spot.Size] - _spotHeights[size]) / 2.0f; if (!spot.AlignToTop) diff *= -1; spot.Size = (Size) size; Vector3 position = Vector3.zero; Quaternion rotation = Quaternion.identity; if (spot.GameObject != null) { position = spot.GameObject.transform.position; rotation = spot.GameObject.transform.rotation; Destroy(spot.GameObject); } spot.GameObject = Instantiate(prefabs[size], position + new Vector3(0, diff, 0), rotation, mainPlanContainer); goto whileEnd; } } } whileEnd:; } } } } private int[] GetFreeSpotCount() { int[] freeSpots = {0, 0, 0, 0}; foreach (var t in _spotMap) foreach (Spot spot in t) if (spot.Free) freeSpots[(int) spot.Size]++; return freeSpots; } private List GetNextCars(int n) { var nextCars = new List(); TimeSpan updatedTime = _currentTime + TimeSpan.FromMinutes(stepTime); while (nextCars.Count < n && updatedTime < EndTime) { foreach (Driver driver in DataImporter.Drivers) if (updatedTime <= driver.Times[1].TimeOfDay && updatedTime > driver.Times[0].TimeOfDay && !driver.Parked && !driver.Rejected && !nextCars.Contains(driver)) nextCars.Add(driver); updatedTime += TimeSpan.FromMinutes(stepTime); } return nextCars; } private void ArrangeSpots(int[,] spotsCreated) { var spotMap = GenerateSpotMap(spotsCreated); float[] spotSizes = {4, 4.5f, 5, 0}; float maxP3 = 0; float maxP2 = 0; foreach (Spot spot in spotMap[2]) if (spot.Size != Size.D) maxP3 = Math.Max(maxP3, _spotHeights[(int) spot.Size]); foreach (Spot spot in spotMap[1]) if (spot.Size != Size.D) maxP2 = Math.Max(maxP2, _spotHeights[(int) spot.Size]); // float maxP3 = spotMap[2].Count == 0 ? 0 : _spotHeights[(int) spotMap[2].Max().Size]; // float maxP2 = spotMap[1].Count == 0 ? 0 : _spotHeights[(int) spotMap[1].Max().Size]; for (int i = 0; i < 4; i++) { float currentY; bool parkingFromTop = i % 2 != 0; switch (i) { case 0: currentY = -Height / 2.0f; break; case 1: currentY = Height / 2.0f - 5.5f - 5.0f - maxP3; break; case 2: currentY = Height / 2.0f - 5.5f - 5.0f - maxP3; break; case 3: currentY = Height / 2.0f; break; default: currentY = -10; break; } float currentX; if (i != 0) currentX = -Width / 2f - 2.25f / 2f + 2.25f; else currentX = -Width / 2f + 5.5f - 2.25f / 2f + 2.25f + 1.75f; bool flipped = false; for (int j = 0; j < spotMap[i].Count; j++) { spotMap[i][j].Flipped = flipped; bool alignTop = i % 2 != 0; switch (spotMap[i][j].Size) { case Size.A: spotMap[i][j].GameObject = Instantiate(Instance.spotPrefabA, mainPlanContainer); break; case Size.B: spotMap[i][j].GameObject = Instantiate(Instance.spotPrefabB, mainPlanContainer); break; case Size.C: spotMap[i][j].GameObject = Instantiate(Instance.spotPrefabC, mainPlanContainer); break; case Size.D: spotMap[i][j].GameObject = Instantiate(Instance.spotPrefabD, mainPlanContainer); spotMap[i][j].GameObject.transform.position = new Vector3(currentX, currentY, 0); break; } spotMap[i][j].GameObject.transform.position = new Vector3(currentX, currentY + (alignTop ? -1 : 1) * spotSizes[(int) spotMap[i][j].Size] / 2.0f, 0); spotMap[i][j].AlignToTop = alignTop; spotMap[i][j].GameObject.transform.rotation = Quaternion.Euler(new Vector3(0, 0, spotMap[i][j].Flipped ? 180 : 0)); bool frontalParking = !parkingFromTop ^ flipped; spotMap[i][j].ParkingDirection = frontalParking ? ParkingPreference.Front : ParkingPreference.Back; spotMap[i][j].Lane = i; currentX += 2.25f; flipped = !flipped; } } } private void GenerateEmergencyPlan() { emergencyPlanContainer.gameObject.SetActive(false); for (int i = 0; i < 4; i++) { float currentY; switch (i) { case 0: currentY = -Height / 2.0f; break; case 1: currentY = Height / 2.0f - 5.5f - 5.0f - 5.0f; break; case 2: currentY = Height / 2.0f - 5.5f - 5.0f - 5.0f; break; case 3: currentY = Height / 2.0f; break; default: currentY = -10; break; } float currentX; if (i != 0) currentX = -Width / 2f - 2.25f / 2f + 2.25f; else currentX = Width / 2f - 5.0f / 2f; bool flipped = false; GameObject spawnedSpot; int[] emergencyMap = new[] {12, 27, 27, 20}; for (int j = 0; j < emergencyMap[i]; j++) { if (i == 0) { Vector3 position = new Vector3(currentX, currentY + 2.25f/2.0f, 0); spawnedSpot = Instantiate(Instance.spotPrefabC, position, Quaternion.Euler(new Vector3(0, 0, 90)), emergencyPlanContainer); currentX -= 5; }else { bool alignTop = i % 2 != 0; Vector3 position = new Vector3(currentX, currentY, 0); spawnedSpot = Instantiate(Instance.spotPrefabC, position, Quaternion.Euler(new Vector3(0, 0, 0)), emergencyPlanContainer); spawnedSpot.transform.position = new Vector3(currentX, currentY + (alignTop ? -1 : 1) * 5.0f / 2.0f, 0); currentX += 2.25f; } } } } private List> GenerateSpotMap(int[,] spotsCreated) { for (int i = 0; i < 4; i++) for (int j = 0; j < spotsCreated.GetLength(1); j++) for (int k = 0; k < spotsCreated[i, j]; k++) { _spotMap[i].Add(new Spot((Size) j, false)); _initialConfigurationSpotCount++; } _spotMap[0].Sort((a, b) => a.Size.CompareTo(b.Size)); // ascending sort _spotMap[1].Sort((a, b) => b.Size.CompareTo(a.Size)); // descending sort _spotMap[2].Sort((a, b) => b.Size.CompareTo(a.Size)); // descending sort _spotMap[3].Sort((a, b) => a.Size.CompareTo(b.Size)); // ascending sort _spotMap[2].Add(new Spot(Size.D, false)); return _spotMap; } } }