// Copyright (C) 2001-2019 Sam Varner
//
// This file is part of Vamos Automotive Simulator.
//
// Vamos is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vamos is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vamos. If not, see <http://www.gnu.org/licenses/>.
#include "../geometry/Numeric.hpp"
#include "Timing_Info.hpp"
#include "World.hpp"
#include <algorithm>
using namespace Vamos_World;
namespace
{
Opt_Sec time(bool finished, const std::vector<double>& times, double current)
{
return finished ? std::nullopt : Opt_Sec(current - (times.empty() ? 0.0 : times.back()));
}
Opt_Sec previous(const std::vector<double>& times)
{
return times.empty() ? std::nullopt
: Opt_Sec(times.back() - (times.size() > 1 ? *(times.end() - 2) : 0.0));
}
const int N_COUNTDOWN_START = 6;
}
Timing_Info::Timing_Info(int n_cars, int n_sectors, bool do_start_sequence)
: m_sectors(n_sectors),
m_countdown(do_start_sequence ? N_COUNTDOWN_START : 0),
m_start_delay(Vamos_Geometry::random_in_range(0.0, 4.0)),
m_state(do_start_sequence ? STARTING : RUNNING)
{
assert(n_sectors > 0);
for (int i = 0; i < n_cars; ++i)
{
ma_car_timing.emplace_back(std::make_unique<Car_Timing>(i + 1, n_sectors));
ma_running_order.push_back(ma_car_timing.back().get());
}
mp_fastest = ma_running_order.front();
}
void Timing_Info::reset()
{
m_total_time = 0.0;
mo_fastest_lap.reset();
ma_running_order.clear();
for (auto& p : ma_car_timing)
{
p->reset();
ma_running_order.push_back(p.get());
}
mp_fastest = ma_running_order.front();
}
void Timing_Info::update(double current_time, size_t index, double distance, int sector)
{
assert(index < ma_car_timing.size());
assert(sector <= m_sectors);
if (m_state == STARTING)
{
double to_go = N_COUNTDOWN_START - (current_time - m_state_time);
m_countdown = std::max(int(to_go + 1), 1);
if (to_go < -m_start_delay)
{
m_countdown = 0;
next_state(current_time);
}
return;
}
assert(m_state == RUNNING || m_state == FINISHED);
m_total_time = current_time - m_state_time;
auto p_car = ma_car_timing[index].get();
bool new_sector = is_new_sector(index, sector);
bool new_lap = new_sector && sector == 1;
bool already_finished = p_car->m_finished;
p_car->update(m_total_time, distance, sector, new_sector);
bool laps_done = m_lap_limit > 0 && ma_car_timing[index]->current_lap() > m_lap_limit;
bool time_done = m_time_limit > 0.0 && m_total_time > m_time_limit;
// A car's race is done when
// 1. it completes all the laps
// 2. it completes any lap after any car has completed all laps
// 3. it completes any lap after time has expired
if (laps_done || (new_lap && (time_done || m_state == FINISHED)))
p_car->m_finished = true;
if (new_sector)
update_position(p_car, m_total_time, sector, already_finished);
// Go to the FINISHED state when the first car has completed all of the laps or
// time runs out. Timing must continue for the other cars, so we keep
// m_state_time.
if (m_state == RUNNING && (laps_done || time_done))
next_state(m_state_time);
}
double Timing_Info::time_remaining() const
{
return m_time_limit == 0.0 ? 0.0 : std::max(m_time_limit - m_total_time, 0.0);
}
void Timing_Info::next_state(double current_time)
{
assert(m_state == STARTING || m_state == RUNNING);
m_state = m_state == STARTING ? RUNNING : FINISHED;
m_state_time = current_time;
}
void Timing_Info::update_position(Car_Timing* p_car, double current_time, int sector, bool finished)
{
if (p_car->previous_sector() == 0 || finished)
return;
assert(sector > 0 && sector <= m_sectors);
auto sector_index = static_cast<size_t>(m_sectors * p_car->laps_complete() + sector - 1);
auto new_position = ma_running_order.begin();
p_car->mo_interval.reset();
if (m_qualifying)
{
if (sector != 1)
return;
for (auto it = ma_running_order.begin();
it != ma_running_order.end() && (*it)->best_lap_time()
&& *p_car->best_lap_time() > *(*it)->best_lap_time();
++it)
++new_position;
}
else if (sector_index > ma_sector_position.size())
{
ma_sector_position.push_back(1);
ma_sector_time.push_back(current_time);
}
else
{
int p = ma_sector_position[sector_index - 1]++;
for (; p > 0; --p)
++new_position;
p_car->mo_interval = current_time - ma_sector_time[sector_index - 1];
ma_sector_time[sector_index - 1] = current_time;
}
// If this car has lost positions it will have been pushed down the running order by
// the cars that have already reached this sector. Thus, old_position is at or after
// new_position.
auto old_position = std::find(new_position, ma_running_order.end(), p_car);
if (new_position != old_position)
{
ma_running_order.insert(new_position, *old_position);
ma_running_order.erase(old_position);
}
auto o_best = p_car->best_lap_time();
if (o_best && (!mo_fastest_lap || *o_best < *mo_fastest_lap))
{
mp_fastest = p_car;
mo_fastest_lap = *o_best;
}
}
bool Timing_Info::is_new_sector(size_t index, int sector) const
{
//!!Fixme: always returns true if there's only one sector.
auto current = ma_car_timing[index]->current_sector();
// Do the % before + because sector is 1-based.
return sector == (current % m_sectors) + 1;
}
Car_Timing::Car_Timing(int position, int sectors)
: m_grid_position(position),
m_sectors(sectors),
mao_best_sector_time(m_sectors),
mao_sector_delta(m_sectors)
{}
void Car_Timing::reset()
{
m_sector = 0;
m_last_sector = 0;
m_lap = 0;
mo_best_lap_time.reset();
mo_lap_delta.reset();
m_finished = false;
ma_lap_time.clear();
ma_sector_time.clear();
for (int sector = 0; sector < m_sectors; ++sector)
{
mao_best_sector_time[sector].reset();
mao_sector_delta[sector].reset();
}
}
void Car_Timing::update(double current_time, double distance, int sector, bool new_sector)
{
m_current_time = current_time;
m_distance = distance;
if (!m_finished && new_sector)
{
if (sector == 1)
update_lap_data(current_time);
update_sector_data(current_time, sector);
}
}
void Car_Timing::update_lap_data(double current_time)
{
++m_lap;
if (m_sector == 0)
return;
ma_lap_time.push_back(current_time);
if (mo_best_lap_time)
{
assert(previous_lap_time());
mo_lap_delta = *previous_lap_time() - *mo_best_lap_time;
if (*mo_lap_delta < 0.0)
mo_best_lap_time = *previous_lap_time();
return;
}
mo_best_lap_time = previous_lap_time();
}
void Car_Timing::update_sector_data(double current_time, int sector)
{
if (m_sector > 0)
ma_sector_time.push_back(current_time);
m_last_sector = m_sector;
m_sector = sector;
if (m_last_sector == 0)
return;
auto index = m_last_sector - 1;
assert(index < m_sectors);
auto& o_best = mao_best_sector_time[index];
if (o_best)
{
assert(previous_sector_time());
mao_sector_delta[index] = *previous_sector_time() - *o_best;
if (*mao_sector_delta[index] < 0.0)
o_best = *previous_sector_time();
return;
}
o_best = ma_sector_time.back()
- (ma_sector_time.size() > 1 ? *(ma_sector_time.end() - 2) : 0.0);
}
Opt_Sec Car_Timing::lap_time() const
{
return time(m_finished, ma_lap_time, m_current_time);
}
Opt_Sec Car_Timing::previous_lap_time() const
{
return previous(ma_lap_time);
}
Opt_Sec Car_Timing::sector_time() const
{
return time(m_finished, ma_sector_time, m_current_time);
}
Opt_Sec Car_Timing::best_sector_time() const
{
return m_sector == 0 ? std::nullopt : mao_best_sector_time[m_sector - 1];
}
Opt_Sec Car_Timing::previous_sector_time() const
{
return previous(ma_sector_time);
}
Opt_Sec Car_Timing::previous_sector_time_difference() const
{
return m_last_sector == 0 ? std::nullopt : mao_sector_delta[m_last_sector - 1];
}