From 1e6b0d7f35e8f4613d368a17ff0e0f2691c9e7b3 Mon Sep 17 00:00:00 2001 From: Jonathan Albert <47063257+jalbert-dev@users.noreply.github.com> Date: Thu, 23 May 2019 23:30:26 -0400 Subject: [PATCH] Fix trail generation in endless mode courses --- src/Course.cpp | 108 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 71 insertions(+), 37 deletions(-) diff --git a/src/Course.cpp b/src/Course.cpp index 1a7c0eda1d..c16ae4c378 100644 --- a/src/Course.cpp +++ b/src/Course.cpp @@ -651,15 +651,12 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) void Course::GetTrailUnsortedEndless( const vector &entries, Trail &trail, StepsType &st, CourseDifficulty &cd, RandomGen &rnd, bool &bCourseDifficultyIsSignificant ) const { - vector vpAllPossibleSongs; - vector vSongAndSteps; - vector vpSongs; typedef vector StepsVector; - map mapSongToSteps; - int songIndex = 0; - bool vpSongsSorted = false; - // Resolve each entry to a Song and Steps. - for (std::vector::const_iterator e = entries.begin(); e != entries.end(); ++e) + + std::set alreadySelected; + Song* lastSongSelected; + vector vSongAndSteps; + for (auto e = entries.begin(); e != entries.end(); ++e) { SongAndSteps resolved; // fill this in @@ -684,8 +681,11 @@ void Course::GetTrailUnsortedEndless( const vector &entries, Trail const bool bSameSongCriteria = e != entries.begin() && ( e - 1 )->songCriteria == soc; const bool bSameStepsCriteria = e != entries.begin() && ( e - 1 )->stepsCriteria == stc; + // If we're doing the same wildcard search as last entry, + // we can just reuse the vSongAndSteps vector. if( pSong ) { + vSongAndSteps.clear(); StepsUtil::GetAllMatchingEndless( pSong, stc, vSongAndSteps ); } else if( vSongAndSteps.empty() || !( bSameSongCriteria && bSameStepsCriteria ) ) @@ -698,40 +698,74 @@ void Course::GetTrailUnsortedEndless( const vector &entries, Trail if( vSongAndSteps.empty() ) continue; - if( !vpSongsSorted && !vSongAndSteps.empty() ) { - vector vpSongs; - typedef vector StepsVector; - map mapSongToSteps; - for (SongAndSteps const &sas : vSongAndSteps) + // if we're doing a RANDOM wildcard search, try to avoid repetition + if (vSongAndSteps.size() > 1 && e->songSort == SongSort::SongSort_Randomize) + { + // Make a backup of the steplist so we can revert if we overfilter + std::vector revertList = vSongAndSteps; + // Filter candidate list via blacklist + vSongAndSteps.erase(std::remove_if( + vSongAndSteps.begin(), + vSongAndSteps.end(), + [&](const SongAndSteps& ss) { + return std::find(alreadySelected.begin(), alreadySelected.end(), ss.pSong) != alreadySelected.end(); + }), + vSongAndSteps.end()); + // If every candidate is in the blacklist, pick random song that wasn't played last + // (Repeat songs may still occur if song after this is fixed; this algorithm doesn't look ahead) + if (vSongAndSteps.empty()) { - StepsVector &v = mapSongToSteps[sas.pSong]; - v.push_back( sas.pSteps ); - if( v.size() == 1 ) - vpSongs.push_back( sas.pSong ); + vSongAndSteps = revertList; + vSongAndSteps.erase(std::remove_if( + vSongAndSteps.begin(), + vSongAndSteps.end(), + [&](const SongAndSteps& ss) { + return ss.pSong == lastSongSelected; + }), + vSongAndSteps.end()); + + // If the song that was played last was the only candidate, forget it + if (vSongAndSteps.empty()) + { + vSongAndSteps = revertList; + } } - vpSongsSorted = true; - CourseSortSongs( e->songSort, vpSongs, rnd ); - songIndex = 0; } - ASSERT( e->iChooseIndex >= 0 ); - if( e->iChooseIndex < static_cast(vSongAndSteps.size()) ) + std::vector vpSongs; + std::map songStepMap; + // Build list of songs for sorting, and mapping of songs to steps simultaneously + for (auto& ss : vSongAndSteps) { - if( songIndex >= int(vpSongs.size()) ) { - songIndex = 0; + StepsVector& stepsForSong = songStepMap[ss.pSong]; + // If we haven't noted this song yet, add it to the song list + if (stepsForSong.size() == 0) + { + vpSongs.push_back(ss.pSong); } - resolved.pSong = vpSongs[ songIndex ]; - const vector &mappedSongs = mapSongToSteps[ resolved.pSong ]; - songIndex++; - resolved.pSteps = mappedSongs[ RandomInt( mappedSongs.size() ) ]; + + stepsForSong.push_back(ss.pSteps); } - else - { + + ASSERT(e->iChooseIndex >= 0); + // If we're trying to pick BEST100 when only 99 songs exist, + // we have a problem, so bail out + if (e->iChooseIndex >= vpSongs.size()) { continue; } - /* If we're not COURSE_DIFFICULTY_REGULAR, then we should be choosing steps that are - * either easier or harder than the base difficulty. If no such steps exist, then + + // Otherwise, pick random steps corresponding to the selected song + CourseSortSongs(e->songSort, vpSongs, rnd); + resolved.pSong = vpSongs[e->iChooseIndex]; + const vector& songSteps = songStepMap[resolved.pSong]; + resolved.pSteps = songSteps[random_up_to(rnd, songSteps.size())]; + + lastSongSelected = resolved.pSong; + alreadySelected.emplace(resolved.pSong); + + /* If we're not COURSE_DIFFICULTY_REGULAR, then we should be choosing steps that are + * either easier or harder than the base difficulty. If no such steps exist, then * just use the one we already have. */ Difficulty dc = resolved.pSteps->GetDifficulty(); int iLowMeter = e->stepsCriteria.m_iLowMeter; @@ -825,11 +859,11 @@ void Course::GetTrailUnsortedEndless( const vector &entries, Trail } trail.m_vEntries.push_back( te ); - if( trail.m_vEntries.size() > 0 && te.dc != cd ) - { - trail.m_vEntries.reserve( trail.m_vEntries.size() - 1 ); - trail.m_vEntries.resize( trail.m_vEntries.size() - 1 ); - } + //if( trail.m_vEntries.size() > 0 && te.dc != cd ) + //{ + // trail.m_vEntries.reserve( trail.m_vEntries.size() - 1 ); + // trail.m_vEntries.resize( trail.m_vEntries.size() - 1 ); + //} // LOG->Trace( "Chose: %s, %d", te.pSong->GetSongDir().c_str(), te.pSteps->GetMeter() ); if( IsAnEdit() && MAX_SONGS_IN_EDIT_COURSE > 0 &&