Skip to content

Commit

Permalink
Break up optimiser files
Browse files Browse the repository at this point in the history
  • Loading branch information
GenericMadScientist committed Apr 25, 2020
1 parent 4ba2c11 commit d4d3e39
Show file tree
Hide file tree
Showing 8 changed files with 618 additions and 539 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function(set_warnings TARGET)
)
endfunction()

add_executable(chopt src/main.cpp src/chart.cpp src/image.cpp src/optimiser.cpp src/points.cpp src/settings.cpp src/sp.cpp src/time.cpp)
add_executable(chopt src/main.cpp src/chart.cpp src/image.cpp src/optimiser.cpp src/points.cpp src/processed.cpp src/settings.cpp src/sp.cpp src/time.cpp)
target_include_directories(chopt PRIVATE "${PROJECT_SOURCE_DIR}/include" "${PROJECT_SOURCE_DIR}/libs")

set_cpp_standard(chopt)
Expand Down
68 changes: 2 additions & 66 deletions include/optimiser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,77 +21,13 @@

#include <map>
#include <optional>
#include <string>
#include <tuple>
#include <vector>

#include "chart.hpp"
#include "points.hpp"
#include "sp.hpp"
#include "processed.hpp"
#include "time.hpp"

struct ActivationCandidate {
PointPtr act_start;
PointPtr act_end;
Position earliest_activation_point {Beat(0.0), Measure(0.0)};
SpBar sp_bar {0.0, 0.0};
};

struct Activation {
PointPtr act_start;
PointPtr act_end;
};

// Part of the return value of ProcessedSong::is_candidate_valid. Says if an
// activation is valid, and if not whether the problem is too little or too much
// Star Power.
enum class ActValidity { success, insufficient_sp, surplus_sp };

// Return value of ProcessedSong::is_candidate_valid, providing information on
// whether an activation is valid, and if so the earliest position it can end.
struct ActResult {
Position ending_position;
ActValidity validity;
};

struct Path {
std::vector<Activation> activations;
int score_boost;
};

// Represents a song processed for Star Power optimisation. The constructor
// should only fail due to OOM; invariants on the song are supposed to be
// upheld by the constructors of the arguments.
class ProcessedSong {
private:
// The order of these members is important. We must have m_converter before
// m_points.
TimeConverter m_converter;
PointSet m_points;
SpData m_sp_data;
int m_total_solo_boost;

public:
ProcessedSong(const NoteTrack& track, int resolution,
const SyncTrack& sync_track, double early_whammy,
double squeeze);

// Return the minimum and maximum amount of SP can be acquired between two
// points. Does not include SP from the point act_start. first_point is
// given for the purposes of counting SP grantings notes, e.g. if start is
// after the middle of first_point's timing window.
[[nodiscard]] SpBar total_available_sp(Beat start, PointPtr first_point,
PointPtr act_start) const;
// Returns an ActResult which says if an activation is valid, and if so the
// earliest position it can end.
[[nodiscard]] ActResult
is_candidate_valid(const ActivationCandidate& activation) const;
// Return the summary of a path.
[[nodiscard]] std::string path_summary(const Path& path) const;

[[nodiscard]] const PointSet& points() const { return m_points; }
[[nodiscard]] const SpData& sp_data() const { return m_sp_data; }
};

// The class that stores extra information needed on top of a ProcessedSong for
// the purposes of optimisation, and finds the optimal path. The song passed to
// Optimiser's constructor must outlive Optimiser; the class is done this way so
Expand Down
93 changes: 93 additions & 0 deletions include/processed.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* chopt - Star Power optimiser for Clone Hero
* Copyright (C) 2020 Raymond Wright
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/

#ifndef CHOPT_PROCESSED_HPP
#define CHOPT_PROCESSED_HPP

#include <string>
#include <vector>

#include "chart.hpp"
#include "points.hpp"
#include "sp.hpp"
#include "time.hpp"

struct ActivationCandidate {
PointPtr act_start;
PointPtr act_end;
Position earliest_activation_point {Beat(0.0), Measure(0.0)};
SpBar sp_bar {0.0, 0.0};
};

struct Activation {
PointPtr act_start;
PointPtr act_end;
};

// Part of the return value of ProcessedSong::is_candidate_valid. Says if an
// activation is valid, and if not whether the problem is too little or too much
// Star Power.
enum class ActValidity { success, insufficient_sp, surplus_sp };

// Return value of ProcessedSong::is_candidate_valid, providing information on
// whether an activation is valid, and if so the earliest position it can end.
struct ActResult {
Position ending_position;
ActValidity validity;
};

struct Path {
std::vector<Activation> activations;
int score_boost;
};

// Represents a song processed for Star Power optimisation. The constructor
// should only fail due to OOM; invariants on the song are supposed to be
// upheld by the constructors of the arguments.
class ProcessedSong {
private:
// The order of these members is important. We must have m_converter before
// m_points.
TimeConverter m_converter;
PointSet m_points;
SpData m_sp_data;
int m_total_solo_boost;

public:
ProcessedSong(const NoteTrack& track, int resolution,
const SyncTrack& sync_track, double early_whammy,
double squeeze);

// Return the minimum and maximum amount of SP can be acquired between two
// points. Does not include SP from the point act_start. first_point is
// given for the purposes of counting SP grantings notes, e.g. if start is
// after the middle of first_point's timing window.
[[nodiscard]] SpBar total_available_sp(Beat start, PointPtr first_point,
PointPtr act_start) const;
// Returns an ActResult which says if an activation is valid, and if so the
// earliest position it can end.
[[nodiscard]] ActResult
is_candidate_valid(const ActivationCandidate& activation) const;
// Return the summary of a path.
[[nodiscard]] std::string path_summary(const Path& path) const;

[[nodiscard]] const PointSet& points() const { return m_points; }
[[nodiscard]] const SpData& sp_data() const { return m_sp_data; }
};

#endif
172 changes: 6 additions & 166 deletions src/optimiser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,179 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#include <algorithm>
#include <cstddef>
#include <iterator>
#include <limits>
#include <numeric>
#include <set>
#include <sstream>

#include "optimiser.hpp"

static constexpr double MEASURES_PER_BAR = 8.0;
static constexpr double MINIMUM_SP_AMOUNT = 0.5;

ProcessedSong::ProcessedSong(const NoteTrack& track, int resolution,
const SyncTrack& sync_track, double early_whammy,
double squeeze)
: m_converter {sync_track, resolution}
, m_points {track, resolution, m_converter, squeeze}
, m_sp_data {track, resolution, sync_track, early_whammy}
{
m_total_solo_boost = std::accumulate(
track.solos().cbegin(), track.solos().cend(), 0,
[](const auto x, const auto& y) { return x + y.value; });
}

SpBar ProcessedSong::total_available_sp(Beat start, PointPtr first_point,
PointPtr act_start) const
{
SpBar sp_bar {0.0, 0.0};
for (auto p = first_point; p < act_start; ++p) {
if (p->is_sp_granting_note) {
sp_bar.add_phrase();
}
}

sp_bar.max() += m_sp_data.available_whammy(start, act_start->position.beat);
sp_bar.max() = std::min(sp_bar.max(), 1.0);

return sp_bar;
}

ActResult
ProcessedSong::is_candidate_valid(const ActivationCandidate& activation) const
{
constexpr double SP_PHRASE_AMOUNT = 0.25;
const Position null_position {Beat(0.0), Measure(0.0)};

if (!activation.sp_bar.full_enough_to_activate()) {
return {null_position, ActValidity::insufficient_sp};
}

auto current_position = activation.act_start->hit_window_end;

auto sp_bar = activation.sp_bar;
sp_bar.min() = std::max(sp_bar.min(), MINIMUM_SP_AMOUNT);

auto starting_meas_diff = current_position.measure
- activation.earliest_activation_point.measure;
sp_bar.min() -= starting_meas_diff.value() / MEASURES_PER_BAR;
sp_bar.min() = std::max(sp_bar.min(), 0.0);

for (auto p = activation.act_start; p < activation.act_end; ++p) {
if (p->is_sp_granting_note) {
auto sp_note_pos = p->hit_window_start;
sp_bar = m_sp_data.propagate_sp_over_whammy(current_position,
sp_note_pos, sp_bar);
if (sp_bar.max() < 0.0) {
return {null_position, ActValidity::insufficient_sp};
}

auto sp_note_end_pos = p->hit_window_end;

auto latest_point_to_hit_sp = m_sp_data.activation_end_point(
sp_note_pos, sp_note_end_pos, sp_bar.max());
sp_bar.min() += SP_PHRASE_AMOUNT;
sp_bar.min() = std::min(sp_bar.min(), 1.0);
sp_bar = m_sp_data.propagate_sp_over_whammy(
sp_note_pos, latest_point_to_hit_sp, sp_bar);
sp_bar.max() += SP_PHRASE_AMOUNT;
sp_bar.max() = std::min(sp_bar.max(), 1.0);

current_position = latest_point_to_hit_sp;
}
}

auto ending_pos = activation.act_end->hit_window_start;
sp_bar = m_sp_data.propagate_sp_over_whammy(current_position, ending_pos,
sp_bar);
if (sp_bar.max() < 0.0) {
return {null_position, ActValidity::insufficient_sp};
}
if (activation.act_end->is_sp_granting_note) {
sp_bar.add_phrase();
}

const auto next_point = std::next(activation.act_end);
if (next_point == m_points.cend()) {
// Return value doesn't matter other than it being non-empty.
auto pos_inf = std::numeric_limits<double>::infinity();
return {{Beat(pos_inf), Measure(pos_inf)}, ActValidity::success};
}

auto end_meas
= ending_pos.measure + Measure(sp_bar.min() * MEASURES_PER_BAR);
if (end_meas >= next_point->hit_window_end.measure) {
return {null_position, ActValidity::surplus_sp};
}

auto end_beat = m_converter.measures_to_beats(end_meas);
return {{end_beat, end_meas}, ActValidity::success};
}

std::string ProcessedSong::path_summary(const Path& path) const
{
// We use std::stringstream instead of std::string for better formating of
// floats (measure values).
std::stringstream stream;
stream << "Path: ";

std::vector<std::string> activation_summaries;
auto start_point = m_points.cbegin();
for (const auto& act : path.activations) {
auto sp_before
= std::count_if(start_point, act.act_start, [](const auto& p) {
return p.is_sp_granting_note;
});
auto sp_during = std::count_if(
act.act_start, std::next(act.act_end),
[](const auto& p) { return p.is_sp_granting_note; });
auto summary = std::to_string(sp_before);
if (sp_during != 0) {
summary += "(+";
summary += std::to_string(sp_during);
summary += ")";
}
activation_summaries.push_back(summary);
start_point = std::next(act.act_end);
}

auto spare_sp
= std::count_if(start_point, m_points.cend(),
[](const auto& p) { return p.is_sp_granting_note; });
if (spare_sp != 0) {
activation_summaries.push_back(std::string("ES")
+ std::to_string(spare_sp));
}

if (activation_summaries.empty()) {
stream << "None";
} else {
stream << activation_summaries[0];
for (std::size_t i = 1; i < activation_summaries.size(); ++i) {
stream << "-" << activation_summaries[i];
}
}

auto no_sp_score = std::accumulate(
m_points.cbegin(), m_points.cend(), 0,
[](const auto x, const auto& y) { return x + y.value; });
no_sp_score += m_total_solo_boost;
stream << "\nNo SP score: " << no_sp_score;

auto total_score = no_sp_score + path.score_boost;
stream << "\nTotal score: " << total_score;

for (std::size_t i = 0; i < path.activations.size(); ++i) {
stream << "\nActivation " << i + 1 << ": Measure "
<< path.activations[i].act_start->position.measure.value() + 1
<< " to Measure "
<< path.activations[i].act_end->position.measure.value() + 1;
}

return stream.str();
}

Optimiser::Optimiser(const ProcessedSong* song)
: m_song {song}
{
Expand Down Expand Up @@ -242,6 +78,8 @@ Optimiser::CacheKey Optimiser::advance_cache_key(CacheKey key) const
PointPtr Optimiser::act_end_lower_bound(PointPtr point, Measure pos,
double sp_bar_amount) const
{
constexpr double MEASURES_PER_BAR = 8.0;

auto end_pos = pos + Measure(MEASURES_PER_BAR * sp_bar_amount);
auto q = std::find_if(point, m_song->points().cend(), [=](const auto& pt) {
return pt.hit_window_end.measure > end_pos;
Expand Down Expand Up @@ -334,6 +172,8 @@ Optimiser::try_previous_best_subpaths(CacheKey key, const Cache& cache,
Optimiser::CacheValue Optimiser::find_best_subpaths(CacheKey key, Cache& cache,
bool has_full_sp) const
{
constexpr double MINIMUM_SP_AMOUNT = 0.5;

auto subpath_from_prev
= try_previous_best_subpaths(key, cache, has_full_sp);
if (subpath_from_prev) {
Expand Down
Loading

0 comments on commit d4d3e39

Please sign in to comment.