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
+79 -26
View File
@@ -91,7 +91,7 @@ float* StepParityCost::getActionCost(State * initialState, State * resultState,
costs[COST_HOLDSWITCH] += calcHoldSwitchCost( initialState, resultState, row, combinedColumns, columnCount); costs[COST_HOLDSWITCH] += calcHoldSwitchCost( initialState, resultState, row, combinedColumns, columnCount);
costs[COST_BRACKETTAP] += calcBracketTapCost( initialState, resultState, row, leftHeel, leftToe, rightHeel, rightToe, elapsedTime, 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 = bool movedLeft =
resultState->didTheFootMove[LEFT_HEEL] || 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_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_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_FACING] += calcFacingCosts( initialState, resultState, combinedColumns, columnCount);
costs[COST_SPIN] += calcSpinCosts(initialState, resultState, combinedColumns, columnCount); costs[COST_SPIN] += calcSpinCosts(initialState, resultState, combinedColumns, columnCount);
costs[COST_FOOTSWITCH] += caclFootswitchCost( initialState, resultState, row, combinedColumns, elapsedTime, columnCount); costs[COST_FOOTSWITCH] += caclFootswitchCost( initialState, resultState, row, combinedColumns, elapsedTime, columnCount);
costs[COST_SIDESWITCH] += calcSideswitchCost( initialState, resultState, columnCount); costs[COST_SIDESWITCH] += calcSideswitchCost( initialState, resultState, columnCount);
costs[COST_MISSED_FOOTSWITCH] += calcMissedFootswitchCost( row, jackedLeft, jackedRight, 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_JACK] += calcJackCost( movedLeft, movedRight, jackedLeft, jackedRight, elapsedTime, 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_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. // I don't like that we're updating columns here like this.
// We're basically updating columns with the final position of the feet // 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; 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 StepParityCost::calcMissedFootswitchCost(Row & row, bool jackedLeft, bool jackedRight, int columnCount)
{ {
float cost = 0; float cost = 0;
@@ -554,13 +590,12 @@ float StepParityCost::calcSpinCosts(State * initialState, State * resultState, s
return cost; return cost;
} }
// Footswitches are harder the slower they are. // Footswitches are harder to do when they get too slow.
// Add a penalty when they get slower than 8ths at 120bpm (0.25 seconds) // 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 StepParityCost::caclFootswitchCost(State * initialState, State * resultState, Row & row, std::vector<StepParity::Foot> & combinedColumns, float elapsedTime, int columnCount)
{ {
float cost = 0; float cost = 0;
// ignore footswitch with 24 or less distance (8th note); penalise slower footswitches based on distance if (elapsedTime >= SLOW_FOOTSWITCH_THRESHOLD && elapsedTime < SLOW_FOOTSWITCH_IGNORE) {
if (elapsedTime >= 0.25) {
// footswitching has no penalty if there's a mine nearby // footswitching has no penalty if there's a mine nearby
if ( if (
std::all_of(row.mines.begin(), row.mines.end(), [](int mine) std::all_of(row.mines.begin(), row.mines.end(), [](int mine)
@@ -568,7 +603,7 @@ float StepParityCost::caclFootswitchCost(State * initialState, State * resultSta
std::all_of(row.fakeMines.begin(), row.fakeMines.end(), [](int mine) std::all_of(row.fakeMines.begin(), row.fakeMines.end(), [](int mine)
{ return mine == 0; })) { return mine == 0; }))
{ {
float timeScaled = elapsedTime - 0.25; float timeScaled = elapsedTime - SLOW_FOOTSWITCH_THRESHOLD;
for (int i = 0; i < columnCount; i++) for (int i = 0; i < columnCount; i++)
{ {
@@ -579,15 +614,15 @@ float StepParityCost::caclFootswitchCost(State * initialState, State * resultSta
if ( if (
initialState->columns[i] != resultState->columns[i] && initialState->columns[i] != resultState->columns[i] &&
!resultState->didTheFootMove[initialState->columns[i]]) initialState->columns[i] != OTHER_PART_OF_FOOT[resultState->columns[i]]
)
{ {
cost += pow(timeScaled / 2.0f, 2) * FOOTSWITCH; cost += (timeScaled / (SLOW_FOOTSWITCH_THRESHOLD + timeScaled)) * FOOTSWITCH;
break; break;
} }
} }
} }
} }
return cost; return cost;
} }
@@ -637,17 +672,35 @@ float StepParityCost::calcBigMovementsQuicklyCost(State * initialState, State *
for (StepParity::Foot foot : resultState->movedFeet) for (StepParity::Foot foot : resultState->movedFeet)
{ {
if(foot == NONE) if(foot == NONE)
{
continue; continue;
int idxFoot = initialState->whereTheFeetAre[foot]; }
if (idxFoot == -1)
int initialPosition = initialState->whereTheFeetAre[foot];
if(initialPosition == -1)
{
continue; continue;
cost += }
(sqrt(
getDistanceSq( int resultPosition = resultState->whereTheFeetAre[foot];
layout[idxFoot],
layout[resultState->whereTheFeetAre[foot]])) *
DISTANCE) / // If we're bracketing something, and the toes are now where the heel
elapsedTime; // 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; return cost;
+19 -4
View File
@@ -11,20 +11,33 @@ namespace StepParity
const int DOUBLESTEP= 850; const int DOUBLESTEP= 850;
const int BRACKETJACK= 20; const int BRACKETJACK= 20;
const int JACK= 30; 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 BRACKETTAP= 400;
const int HOLDSWITCH= 55; const int HOLDSWITCH= 55;
const int MINE= 10000; const int MINE= 10000;
const int FOOTSWITCH= 5000; const int FOOTSWITCH= 325;
const int MISSED_FOOTSWITCH= 500; const int MISSED_FOOTSWITCH= 500;
const int FACING= 2; const int FACING= 2;
const int DISTANCE= 6; const int DISTANCE= 6;
const int SPIN= 1000; const int SPIN= 1000;
const int SIDESWITCH= 130; const int SIDESWITCH= 130;
const int CROWDED_BRACKET = 40; const int CROWDED_BRACKET = 0;
const int OTHER = 500; 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; 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 class StepParityCost
{ {
@@ -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 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 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 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 calcMissedFootswitchCost(Row &row, bool jackedLeft, bool jackedRight, int columnCount);
float calcFacingCosts(State * initialState, State * resultState, std::vector<StepParity::Foot> &combinedColumns, 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); 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_BRACKETJACK,
COST_JACK, COST_JACK,
COST_JUMP, COST_JUMP,
COST_SLOW_BRACKET,
COST_TWISTED_FOOT,
COST_BRACKETTAP, COST_BRACKETTAP,
COST_HOLDSWITCH, COST_HOLDSWITCH,
COST_MINE, COST_MINE,
+17 -3
View File
@@ -23,9 +23,18 @@ XToLocalizedString( TechCountsCategory );
LuaFunction(TechCountsCategoryToLocalizedString, TechCountsCategoryToLocalizedString(Enum::Check<TechCountsCategory>(L, 1)) ); LuaFunction(TechCountsCategoryToLocalizedString, TechCountsCategoryToLocalizedString(Enum::Check<TechCountsCategory>(L, 1)) );
LuaXType( TechCountsCategory ); 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() TechCounts::TechCounts()
{ {
MakeUnknown(); MakeUnknown();
@@ -104,6 +113,7 @@ void TechCounts::CalculateTechCountsFromRows(const std::vector<StepParity::Row>
const StepParity::Row &previousRow = rows[i - 1]; const StepParity::Row &previousRow = rows[i - 1];
int noteCount = 0; int noteCount = 0;
float elapsedTime = currentRow.second - previousRow.second;
// copy the foot placement for the current row into currentColumns, // copy the foot placement for the current row into currentColumns,
// and count up how many notes there are in this row // and count up how many notes there are in this row
@@ -145,17 +155,20 @@ void TechCounts::CalculateTechCountsFromRows(const std::vector<StepParity::Row>
if(previousFootPlacement[foot] == currentFootPlacement[foot]) if(previousFootPlacement[foot] == currentFootPlacement[foot])
{ {
if(currentRow.second - previousRow.second < JACK_CUTOFF) if(elapsedTime < JACK_CUTOFF)
{ {
out[TechCountsCategory_Jacks] += 1; out[TechCountsCategory_Jacks] += 1;
} }
} }
else else
{
if(elapsedTime < DOUBLESTEP_CUTOFF)
{ {
out[TechCountsCategory_Doublesteps] += 1; out[TechCountsCategory_Doublesteps] += 1;
} }
} }
} }
}
// check for brackets // check for brackets
if(noteCount >= 2) if(noteCount >= 2)
@@ -183,6 +196,7 @@ void TechCounts::CalculateTechCountsFromRows(const std::vector<StepParity::Row>
if(previousColumns[c] != StepParity::NONE if(previousColumns[c] != StepParity::NONE
&& previousColumns[c] != currentColumns[c] && previousColumns[c] != currentColumns[c]
&& StepParity::OTHER_PART_OF_FOOT[previousColumns[c]] != currentColumns[c] && StepParity::OTHER_PART_OF_FOOT[previousColumns[c]] != currentColumns[c]
&& elapsedTime < FOOTSWITCH_CUTOFF
) )
{ {
// this is assuming only 4-panel single // this is assuming only 4-panel single