From 5f7720a8b8e316ef4c92677ec8f7eddefd33c8c9 Mon Sep 17 00:00:00 2001 From: Michael Votaw Date: Sat, 7 Sep 2024 12:05:02 -0500 Subject: [PATCH] Added costs for "slow" bracketing (to prioritize jumps), twisted backwards foot. Removed costs for JUMP, CROWDED_BRACKET, and OTHER Added cutoffs for counting footswitches and doublesteps below a certain speed as tech --- src/StepParityCost.cpp | 137 +++++++++++++++++++++++++----------- src/StepParityCost.h | 27 +++++-- src/StepParityDatastructs.h | 2 + src/TechCounts.cpp | 22 ++++-- 4 files changed, 136 insertions(+), 52 deletions(-) diff --git a/src/StepParityCost.cpp b/src/StepParityCost.cpp index 133f45b58e..1110542331 100644 --- a/src/StepParityCost.cpp +++ b/src/StepParityCost.cpp @@ -91,7 +91,7 @@ float* StepParityCost::getActionCost(State * initialState, State * resultState, costs[COST_HOLDSWITCH] += calcHoldSwitchCost( initialState, resultState, row, combinedColumns, columnCount); costs[COST_BRACKETTAP] += calcBracketTapCost( initialState, resultState, row, leftHeel, leftToe, rightHeel, rightToe, elapsedTime, columnCount); - costs[COST_OTHER] += calcMovingFootWhileOtherIsntOnPadCost( initialState, resultState, columnCount); +// costs[COST_OTHER] += calcMovingFootWhileOtherIsntOnPadCost( initialState, resultState, columnCount); bool movedLeft = resultState->didTheFootMove[LEFT_HEEL] || @@ -121,20 +121,17 @@ float* StepParityCost::getActionCost(State * initialState, State * resultState, costs[COST_BRACKETJACK] += calcBracketJackCost( initialState, resultState, rows, rowIndex, movedLeft, movedRight, jackedLeft, jackedRight, didJump, columnCount); costs[COST_DOUBLESTEP] += calcDoublestepCost(initialState, resultState, rows, rowIndex, movedLeft, movedRight, jackedLeft, jackedRight, didJump, columnCount); - costs[COST_JUMP] += calcJumpCost( row, movedLeft, movedRight, elapsedTime, columnCount); +// costs[COST_JUMP] += calcJumpCost( row, movedLeft, movedRight, elapsedTime, columnCount); + costs[COST_SLOW_BRACKET] += calcSlowBracketCost(row, movedLeft, movedRight, elapsedTime); + costs[COST_TWISTED_FOOT] += calcTwistedFootCost(resultState); costs[COST_FACING] += calcFacingCosts( initialState, resultState, combinedColumns, columnCount); costs[COST_SPIN] += calcSpinCosts(initialState, resultState, combinedColumns, columnCount); costs[COST_FOOTSWITCH] += caclFootswitchCost( initialState, resultState, row, combinedColumns, elapsedTime, columnCount); costs[COST_SIDESWITCH] += calcSideswitchCost( initialState, resultState, columnCount); - costs[COST_MISSED_FOOTSWITCH] += calcMissedFootswitchCost( row, jackedLeft, jackedRight, columnCount); - - // To do: small weighting for swapping heel with toe or toe with heel (both add up) - // To do: huge weighting for having foot direction opposite of eachother (can't twist one leg 180 degrees) + costs[COST_MISSED_FOOTSWITCH] += calcMissedFootswitchCost( row, jackedLeft, jackedRight, columnCount) costs[COST_JACK] += calcJackCost( movedLeft, movedRight, jackedLeft, jackedRight, elapsedTime, columnCount); - - // To do: weighting for moving a foot a far distance in a fast time costs[COST_DISTANCE] += calcBigMovementsQuicklyCost( initialState, resultState, elapsedTime, columnCount); - costs[COST_CROWDED_BRACKET] += calcCrowdedBracketCost(initialState, resultState, elapsedTime, columnCount); +// costs[COST_CROWDED_BRACKET] += calcCrowdedBracketCost(initialState, resultState, elapsedTime, columnCount); // I don't like that we're updating columns here like this. // We're basically updating columns with the final position of the feet @@ -405,6 +402,45 @@ float StepParityCost::calcJumpCost(Row & row, bool movedLeft, bool movedRight, f return cost; } +// Jumps should be prioritized over brackets below a certain speed +float StepParityCost::calcSlowBracketCost(Row & row, bool movedLeft, bool movedRight, float elapsedTime) +{ + float cost = 0; + if(elapsedTime > SLOW_BRACKET_THRESHOLD && movedLeft != movedRight && + std::count_if(row.notes.begin(), row.notes.end(), [](StepParity::IntermediateNoteData note) + { return note.type != TapNoteType_Empty; }) >= 2) + { + float timediff = elapsedTime - SLOW_BRACKET_THRESHOLD; + cost += timediff * SLOW_BRACKET; + } + return cost; +} + +// Does this placement result in one of the feet being twisted around? +// This should probably be getting filtered out as an invalid positioning before +// we even get to calculating costs. +float StepParityCost::calcTwistedFootCost(State * resultState) +{ + float cost = 0; + int leftHeel = resultState->whereTheFeetAre[LEFT_HEEL]; + int leftToe = resultState->whereTheFeetAre[LEFT_TOE]; + int rightHeel = resultState->whereTheFeetAre[RIGHT_HEEL]; + int rightToe = resultState->whereTheFeetAre[RIGHT_TOE]; + + StagePoint leftPos = averagePoint(leftHeel, leftToe); + StagePoint rightPos = averagePoint(rightHeel, rightToe); + + bool crossedOver = rightPos.x < leftPos.x; + bool rightBackwards = rightHeel != -1 && rightToe != -1 ? layout[rightToe].y < layout[rightHeel].y : false; + bool leftBackwards = leftHeel != -1 && leftToe != -1 ? layout[leftToe].y < layout[leftHeel].y : false; + + if(!crossedOver && (rightBackwards || leftBackwards)) + { + cost += TWISTED_FOOT; + } + return cost; +} + float StepParityCost::calcMissedFootswitchCost(Row & row, bool jackedLeft, bool jackedRight, int columnCount) { float cost = 0; @@ -554,40 +590,39 @@ float StepParityCost::calcSpinCosts(State * initialState, State * resultState, s return cost; } -// Footswitches are harder the slower they are. -// Add a penalty when they get slower than 8ths at 120bpm (0.25 seconds) +// Footswitches are harder to do when they get too slow. +// Notes with an elapsed time greater than this will incur a penalty float StepParityCost::caclFootswitchCost(State * initialState, State * resultState, Row & row, std::vector & combinedColumns, float elapsedTime, int columnCount) { float cost = 0; - // ignore footswitch with 24 or less distance (8th note); penalise slower footswitches based on distance - if (elapsedTime >= 0.25) { + if (elapsedTime >= SLOW_FOOTSWITCH_THRESHOLD && elapsedTime < SLOW_FOOTSWITCH_IGNORE) { // footswitching has no penalty if there's a mine nearby - if ( - std::all_of(row.mines.begin(), row.mines.end(), [](int mine) - { return mine == 0; }) && - std::all_of(row.fakeMines.begin(), row.fakeMines.end(), [](int mine) - { return mine == 0; })) - { - float timeScaled = elapsedTime - 0.25; - - for (int i = 0; i < columnCount; i++) + if ( + std::all_of(row.mines.begin(), row.mines.end(), [](int mine) + { return mine == 0; }) && + std::all_of(row.fakeMines.begin(), row.fakeMines.end(), [](int mine) + { return mine == 0; })) { - if ( - initialState->columns[i] == NONE || - resultState->columns[i] == NONE) - continue; + float timeScaled = elapsedTime - SLOW_FOOTSWITCH_THRESHOLD; - if ( - initialState->columns[i] != resultState->columns[i] && - !resultState->didTheFootMove[initialState->columns[i]]) + for (int i = 0; i < columnCount; i++) { - cost += pow(timeScaled / 2.0f, 2) * FOOTSWITCH; - break; + if ( + initialState->columns[i] == NONE || + resultState->columns[i] == NONE) + continue; + + if ( + initialState->columns[i] != resultState->columns[i] && + initialState->columns[i] != OTHER_PART_OF_FOOT[resultState->columns[i]] + ) + { + cost += (timeScaled / (SLOW_FOOTSWITCH_THRESHOLD + timeScaled)) * FOOTSWITCH; + break; + } } } - } } - return cost; } @@ -637,17 +672,35 @@ float StepParityCost::calcBigMovementsQuicklyCost(State * initialState, State * for (StepParity::Foot foot : resultState->movedFeet) { if(foot == NONE) + { continue; - int idxFoot = initialState->whereTheFeetAre[foot]; - if (idxFoot == -1) + } + + int initialPosition = initialState->whereTheFeetAre[foot]; + if(initialPosition == -1) + { continue; - cost += - (sqrt( - getDistanceSq( - layout[idxFoot], - layout[resultState->whereTheFeetAre[foot]])) * - DISTANCE) / - elapsedTime; + } + + int resultPosition = resultState->whereTheFeetAre[foot]; + + + // If we're bracketing something, and the toes are now where the heel + // was, then we don't need to worry about it, we're not actually moving + // the foot very far + bool isBracketing = resultState->whereTheFeetAre[OTHER_PART_OF_FOOT[foot]] != -1; + if(isBracketing && resultState->whereTheFeetAre[OTHER_PART_OF_FOOT[foot]] == initialPosition) + { + continue; + } + + float dist = (sqrt(getDistanceSq(layout[initialPosition], layout[resultPosition])) * DISTANCE) / elapsedTime; + // Otherwise if we're still bracketing, this is probably a less drastic movement + if(isBracketing) + { + dist = dist * 0.2; + } + cost += dist; } return cost; diff --git a/src/StepParityCost.h b/src/StepParityCost.h index ee331d2617..33725636a8 100644 --- a/src/StepParityCost.h +++ b/src/StepParityCost.h @@ -11,21 +11,34 @@ namespace StepParity const int DOUBLESTEP= 850; const int BRACKETJACK= 20; const int JACK= 30; - const int JUMP= 30; + const int JUMP= 0; + const int SLOW_BRACKET=300; + const int TWISTED_FOOT=100000; const int BRACKETTAP= 400; const int HOLDSWITCH= 55; const int MINE= 10000; - const int FOOTSWITCH= 5000; + const int FOOTSWITCH= 325; const int MISSED_FOOTSWITCH= 500; const int FACING= 2; const int DISTANCE= 6; const int SPIN= 1000; const int SIDESWITCH= 130; - const int CROWDED_BRACKET = 40; - const int OTHER = 500; - - const float JACK_THRESHOLD = 0.1; + const int CROWDED_BRACKET = 0; + const int OTHER = 0; + // 0.1 = 1/16th note at 150bpm. Jacks quicker than this are harder. + // Above this speed, footswitches should be prioritized + const float JACK_THRESHOLD = 0.1; + // 0.15 = 1/8th at 200bpm, or 3/16th at 150bpm. Below this speed, jumps should be prioritized + const float SLOW_BRACKET_THRESHOLD = 0.15; + + // 0.2 = 1/8th at 150bpm. Footswitches slower than this are harder. + // Below this speed, jacks should be prioritized + const float SLOW_FOOTSWITCH_THRESHOLD = 0.2; + // 0.4 = 1/4th at 150bpm. Once a footswitch gets slow enough, though, + // just ignore it. + const float SLOW_FOOTSWITCH_IGNORE = 0.4; + class StepParityCost { private: @@ -54,6 +67,8 @@ namespace StepParity float calcBracketJackCost(State * initialState, State * resultState, std::vector &rows, int rowIndex, bool movedLeft, bool movedRight, bool jackedLeft, bool jackedRight, bool didJump, int columnCount); float calcDoublestepCost(State * initialState, State * resultState, std::vector & rows, int rowIndex, bool movedLeft, bool movedRight, bool jackedLeft, bool jackedRight, bool didJump, int columnCount); float calcJumpCost(Row &row, bool movedLeft, bool movedRight, float elapsedTime, int columnCount); + float calcSlowBracketCost(Row & row, bool movedLeft, bool movedRight, float elapsedTime); + float calcTwistedFootCost(State * resultState); float calcMissedFootswitchCost(Row &row, bool jackedLeft, bool jackedRight, int columnCount); float calcFacingCosts(State * initialState, State * resultState, std::vector &combinedColumns, int columnCount); float calcSpinCosts(State * initialState, State * resultState, std::vector & combinedColumns, int columnCount); diff --git a/src/StepParityDatastructs.h b/src/StepParityDatastructs.h index 98842b57c3..f57cf957fc 100644 --- a/src/StepParityDatastructs.h +++ b/src/StepParityDatastructs.h @@ -39,6 +39,8 @@ namespace StepParity { COST_BRACKETJACK, COST_JACK, COST_JUMP, + COST_SLOW_BRACKET, + COST_TWISTED_FOOT, COST_BRACKETTAP, COST_HOLDSWITCH, COST_MINE, diff --git a/src/TechCounts.cpp b/src/TechCounts.cpp index 64f1029bf1..4e3d528c3c 100644 --- a/src/TechCounts.cpp +++ b/src/TechCounts.cpp @@ -23,9 +23,18 @@ XToLocalizedString( TechCountsCategory ); LuaFunction(TechCountsCategoryToLocalizedString, TechCountsCategoryToLocalizedString(Enum::Check(L, 1)) ); LuaXType( TechCountsCategory ); -const float JACK_CUTOFF = 0.176; // 170bpm 1/8th notes, anything slower isn't considered a jack -// TechCounts methods +// 0.176 ~= 1/8th at 175bpm +// Anything slower isn't counted as a jack +const float JACK_CUTOFF = 0.176; +// 0.3 = 1/4th at 200bpm (or 3/16th at 150bpm) +// Anything slower isn't counted as a footswitch +const float FOOTSWITCH_CUTOFF = 0.3; +// 0.235 ~= 1/8th at 128bpm +// Anything slower isn't counted as a doublestep +const float DOUBLESTEP_CUTOFF = 0.235; + +// TechCounts methods TechCounts::TechCounts() { MakeUnknown(); @@ -104,6 +113,7 @@ void TechCounts::CalculateTechCountsFromRows(const std::vector const StepParity::Row &previousRow = rows[i - 1]; int noteCount = 0; + float elapsedTime = currentRow.second - previousRow.second; // copy the foot placement for the current row into currentColumns, // and count up how many notes there are in this row @@ -145,14 +155,17 @@ void TechCounts::CalculateTechCountsFromRows(const std::vector if(previousFootPlacement[foot] == currentFootPlacement[foot]) { - if(currentRow.second - previousRow.second < JACK_CUTOFF) + if(elapsedTime < JACK_CUTOFF) { out[TechCountsCategory_Jacks] += 1; } } else { - out[TechCountsCategory_Doublesteps] += 1; + if(elapsedTime < DOUBLESTEP_CUTOFF) + { + out[TechCountsCategory_Doublesteps] += 1; + } } } } @@ -183,6 +196,7 @@ void TechCounts::CalculateTechCountsFromRows(const std::vector if(previousColumns[c] != StepParity::NONE && previousColumns[c] != currentColumns[c] && StepParity::OTHER_PART_OF_FOOT[previousColumns[c]] != currentColumns[c] + && elapsedTime < FOOTSWITCH_CUTOFF ) { // this is assuming only 4-panel single