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
This commit is contained in:
Michael Votaw
2025-02-11 19:39:03 -08:00
committed by teejusb
parent f70cb4c6aa
commit 5f7720a8b8
4 changed files with 136 additions and 52 deletions
+95 -42
View File
@@ -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<StepParity::Foot> & 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;
+21 -6
View File
@@ -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<Row> &rows, int rowIndex, bool movedLeft, bool movedRight, bool jackedLeft, bool jackedRight, bool didJump, int columnCount);
float calcDoublestepCost(State * initialState, State * resultState, std::vector<Row> & 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<StepParity::Foot> &combinedColumns, int columnCount);
float calcSpinCosts(State * initialState, State * resultState, std::vector<StepParity::Foot> & combinedColumns, int columnCount);
+2
View File
@@ -39,6 +39,8 @@ namespace StepParity {
COST_BRACKETJACK,
COST_JACK,
COST_JUMP,
COST_SLOW_BRACKET,
COST_TWISTED_FOOT,
COST_BRACKETTAP,
COST_HOLDSWITCH,
COST_MINE,
+18 -4
View File
@@ -23,9 +23,18 @@ XToLocalizedString( TechCountsCategory );
LuaFunction(TechCountsCategoryToLocalizedString, TechCountsCategoryToLocalizedString(Enum::Check<TechCountsCategory>(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<StepParity::Row>
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<StepParity::Row>
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<StepParity::Row>
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