Parking/Assets/Scripts/ParkingManager.cs
2022-09-04 13:36:19 +02:00

357 lines
14 KiB
C#

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;
private readonly float[] _spotHeights = {3.5f, 4f, 5f, 7.5f};
private readonly List<List<Spot>> _spotMap = new()
{new List<Spot>(), new List<Spot>(), new List<Spot>(), new List<Spot>()};
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]}";
DataImporter.ReadFile("Assets/Data/Tablica1.csv");
InitialConfigurationGenerator generator = new();
ArrangeSpots(generator.FindSolution());
ReserveInitialSpots();
}
private void ReserveInitialSpots()
{
for (var j = 3; j >= 0; j--)
foreach (Spot spot in _spotMap[j]) {
spot.Reserved = false;
spot.ReservedPriority = 0;
}
for (var i = 0; i < Math.Min(DataImporter.Drivers.Count, _initialConfigurationSpotCount); i++) {
bool found = false;
// Find spot
for (var 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;
ReserveInitialSpots();
ResetDrivers();
_rejectedDrivers = new[] {0, 0, 0, 0};
rejectedText.text = $"Małe: {_rejectedDrivers[0]} Średnie: {_rejectedDrivers[1]} " +
$"Duże: {_rejectedDrivers[2]}";
timeText.text = _currentTime.ToString();
return;
}
timeText.text = _currentTime.ToString();
foreach (Driver driver in DataImporter.Drivers) {
// TODO: Check if car can stay before other reservation - may not work for priority-based reservations
var triesToPark = _currentTime <= driver.Times[3].TimeOfDay && _currentTime > driver.Times[2].TimeOfDay;
var 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 (List<Spot> 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;
}
else
Debug.Log("Spot should be reserved but is taken");
}
foreach (var t in _spotMap)
foreach (Spot spot in t) {
var 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<TextMeshProUGUI>().text = driver.Number.ToString();
driver.GameObject.GetComponentInChildren<TextMeshProUGUI>().transform.rotation =
Quaternion.Euler(new Vector3(0, 0, spot.Flipped ? 180 : 0));
driver.GameObject.transform.position = spot.GameObject.transform.position;
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()
{
var 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 (var 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...");
}
}
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<Driver> GetNextCars(int n)
{
var nextCars = new List<Driver>();
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);
var maxP3 = _spotHeights[(int) spotMap[2].Max().Size];
var maxP2 = _spotHeights[(int) spotMap[1].Max().Size];
for (var i = 0; i < 4; i++) {
float currentY;
switch (i) {
case 0:
currentY = -Height / 2.0f + 2f;
break;
case 1:
currentY = Height / 2 - 5.5f - 5f - maxP3 / 2.0f - maxP2;
break;
case 2:
currentY = Height / 2 - 5.5f - 5f - maxP3 / 2.0f;
break;
case 3:
currentY = Height / 2.0f - 2f;
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;
var flipped = false;
var parkingFromTop = i % 2 != 0;
for (var j = 0; j < spotMap[i].Count; j++) {
spotMap[i][j].Flipped = flipped;
var alignTop = i % 2 != 0;
switch (spotMap[i][j].Size) {
case Size.A:
spotMap[i][j].GameObject = Instantiate(Instance.spotPrefabA);
spotMap[i][j].GameObject.transform.position = new Vector3(currentX, currentY, 0);
break;
case Size.B:
spotMap[i][j].GameObject = Instantiate(Instance.spotPrefabB);
spotMap[i][j].GameObject.transform.position =
new Vector3(currentX, currentY + (alignTop ? -1 : 1) * 0.25f, 0);
break;
case Size.C:
spotMap[i][j].GameObject = Instantiate(Instance.spotPrefabC);
spotMap[i][j].GameObject.transform.position =
new Vector3(currentX, currentY + (alignTop ? -1 : 1) * 0.5f, 0);
break;
case Size.D:
spotMap[i][j].GameObject = Instantiate(Instance.spotPrefabD);
spotMap[i][j].GameObject.transform.position =
new Vector3(currentX, currentY - 2.0f, 0);
break;
}
spotMap[i][j].GameObject.transform.rotation =
Quaternion.Euler(new Vector3(0, 0, spotMap[i][j].Flipped ? 180 : 0));
var frontalParking = !parkingFromTop ^ flipped;
spotMap[i][j].ParkingDirection = frontalParking ? ParkingPreference.Front : ParkingPreference.Back;
currentX += 2.25f;
flipped = !flipped;
}
}
}
private List<List<Spot>> GenerateSpotMap(int[,] spotsCreated)
{
for (var i = 0; i < 4; i++)
for (var j = 0; j < spotsCreated.GetLength(1); j++)
for (var 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;
}
}
}