From 4e7432a5a07d7de11b4fb1c39104d071950651db Mon Sep 17 00:00:00 2001 From: Michael Votaw Date: Thu, 3 Oct 2024 22:49:17 -0500 Subject: [PATCH] Several "structural" changes, and some memory optimizations: - Replaced use of bare '-1' values with StepParity::INVALID_COLUMN - Removed StepParityGraph object, moved its responsibilities to StepParityGenerator - Removed some unnecessary data from State object, added 'combinedColumns' and 'whatNoteTheFootIsHitting' - Created stateCache to allow reuse of state objects - Fixed a very small bug with TechCounts (missing 'previousPreviousHeel != INVALID_COLUMN') --- src/StepParityCost.cpp | 234 +++++++++++------------------ src/StepParityCost.h | 13 +- src/StepParityDatastructs.cpp | 235 ++--------------------------- src/StepParityDatastructs.h | 113 +++----------- src/StepParityGenerator.cpp | 275 ++++++++++++++++++++++++---------- src/StepParityGenerator.h | 44 +++++- src/TechCounts.cpp | 17 ++- 7 files changed, 375 insertions(+), 556 deletions(-) diff --git a/src/StepParityCost.cpp b/src/StepParityCost.cpp index 5be00c6976..d7542bebe4 100644 --- a/src/StepParityCost.cpp +++ b/src/StepParityCost.cpp @@ -20,23 +20,18 @@ bool isEmpty(const std::vector & vec, int columnCount) { } -float StepParityCost::getActionCost(State * initialState, State * resultState, std::vector& rows, int rowIndex) +float StepParityCost::getActionCost(State * initialState, State * resultState, std::vector& rows, int rowIndex, float elapsedTime) { Row &row = rows[rowIndex]; int columnCount = row.columnCount; - float elapsedTime = resultState->second - initialState->second; float cost = 0; - std::vector combinedColumns(columnCount, NONE); - - mergeInitialAndResultPosition(initialState, resultState, combinedColumns, columnCount); - // Mine weighting - int leftHeel = -1; - int leftToe = -1; - int rightHeel = -1; - int rightToe = -1; + int leftHeel = INVALID_COLUMN; + int leftToe = INVALID_COLUMN; + int rightHeel = INVALID_COLUMN; + int rightToe = INVALID_COLUMN; for (int i = 0; i < columnCount; i++) { switch (resultState->columns[i]) { @@ -81,79 +76,24 @@ float StepParityCost::getActionCost(State * initialState, State * resultState, s bool jackedLeft = didJackLeft(initialState, resultState, leftHeel, leftToe, movedLeft, didJump, columnCount); bool jackedRight = didJackRight(initialState, resultState, rightHeel, rightToe, movedRight, didJump, columnCount); - cost += calcMineCost( initialState, resultState, row, combinedColumns, columnCount); - cost += calcHoldSwitchCost( initialState, resultState, row, combinedColumns, columnCount); + cost += calcMineCost( initialState, resultState, row, columnCount); + cost += calcHoldSwitchCost( initialState, resultState, row, columnCount); cost += calcBracketTapCost( initialState, resultState, row, leftHeel, leftToe, rightHeel, rightToe, elapsedTime, columnCount); cost += calcBracketJackCost( initialState, resultState, rows, rowIndex, movedLeft, movedRight, jackedLeft, jackedRight, didJump, columnCount); cost += calcDoublestepCost(initialState, resultState, rows, rowIndex, movedLeft, movedRight, jackedLeft, jackedRight, didJump, columnCount); cost += calcSlowBracketCost(row, movedLeft, movedRight, elapsedTime); cost += calcTwistedFootCost(resultState); - cost += calcFacingCosts( initialState, resultState, combinedColumns, columnCount); - cost += calcSpinCosts(initialState, resultState, combinedColumns, columnCount); - cost += caclFootswitchCost( initialState, resultState, row, combinedColumns, elapsedTime, columnCount); + cost += calcFacingCosts( initialState, resultState, columnCount); + cost += calcSpinCosts(initialState, resultState, columnCount); + cost += caclFootswitchCost( initialState, resultState, row, elapsedTime, columnCount); cost += calcSideswitchCost( initialState, resultState, columnCount); cost += calcMissedFootswitchCost( row, jackedLeft, jackedRight, columnCount); cost += calcJackCost( movedLeft, movedRight, jackedLeft, jackedRight, elapsedTime, columnCount); cost += calcBigMovementsQuicklyCost( 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 - // for the next iteration when this is initialState - resultState->columns = combinedColumns; - for(int i = 0; i < columnCount; i++) - { - if(combinedColumns[i] >= NONE) - { - resultState->whereTheFeetAre[combinedColumns[i]] = i; - } - } - return cost; } -// This merges the `columns` properties of initialState and resultState, which -// fully represents the player's position on the dance stage. -// For example: -// initialState.columns = [1,0,0,3] -// resultState.columns = [0,1,0,0] -// combinedColumns = [0,1,0,3] -// This eventually gets saved back to resultState -void StepParityCost::mergeInitialAndResultPosition(State * initialState, State * resultState, std::vector & combinedColumns, int columnCount) -{ - // Merge initial + result position - for (int i = 0; i < columnCount; i++) { - // copy in data from resultState over the top which overrides it, as long as it's not nothing - if (resultState->columns[i] != NONE) { - combinedColumns[i] = resultState->columns[i]; - continue; - } - - // copy in data from initialState, if it wasn't moved - if ( - initialState->columns[i] == LEFT_HEEL || - initialState->columns[i] == RIGHT_HEEL - ) { - if (!resultState->didTheFootMove[initialState->columns[i]]) { - combinedColumns[i] = initialState->columns[i]; - } - } else if (initialState->columns[i] == LEFT_TOE) { - if ( - !resultState->didTheFootMove[LEFT_TOE] && - !resultState->didTheFootMove[LEFT_HEEL] - ) { - combinedColumns[i] = initialState->columns[i]; - } - } else if (initialState->columns[i] == RIGHT_TOE) { - if ( - !resultState->didTheFootMove[RIGHT_TOE] && - !resultState->didTheFootMove[RIGHT_HEEL] - ) { - combinedColumns[i] = initialState->columns[i]; - } - } - } -} - // Calculate the cost of avoiding a mine before the current step // If a mine occurred just before a step, add to the cost // ex: @@ -162,12 +102,12 @@ void StepParityCost::mergeInitialAndResultPosition(State * initialState, State * // // 00M0 // 0100 <- no cost -float StepParityCost::calcMineCost(State * initialState, State * resultState, Row &row, std::vector& combinedColumns, int columnCount) +float StepParityCost::calcMineCost(State * initialState, State * resultState, Row &row, int columnCount) { float cost = 0; for (int i = 0; i < columnCount; i++) { - if (combinedColumns[i] != NONE && row.mines[i] != 0) { + if (resultState->combinedColumns[i] != NONE && row.mines[i] != 0) { cost += MINE; break; } @@ -179,7 +119,7 @@ float StepParityCost::calcMineCost(State * initialState, State * resultState, Ro // Multiply the HOLDSWITCH cost by the distance that the "intial" foot // that was holding the note had to travel to it's new position. // If the initial foot doesn't move anywhere, then don't mulitply it by anything. -float StepParityCost::calcHoldSwitchCost(State * initialState, State * resultState, Row &row, std::vector & combinedColumns, int columnCount) +float StepParityCost::calcHoldSwitchCost(State * initialState, State * resultState, Row &row, int columnCount) { float cost = 0; for (int c = 0; c < columnCount; c++) @@ -187,18 +127,18 @@ float StepParityCost::calcHoldSwitchCost(State * initialState, State * resultSta if (row.holds[c].type == TapNoteType_Empty) continue; if ( - ((combinedColumns[c] == LEFT_HEEL || - combinedColumns[c] == LEFT_TOE) && - initialState->columns[c] != LEFT_TOE && - initialState->columns[c] != LEFT_HEEL) || - ((combinedColumns[c] == RIGHT_HEEL || - combinedColumns[c] == RIGHT_TOE) && - initialState->columns[c] != RIGHT_TOE && - initialState->columns[c] != RIGHT_HEEL)) { - int previousFoot =initialState->whereTheFeetAre[combinedColumns[c]]; + ((resultState->combinedColumns[c] == LEFT_HEEL || + resultState->combinedColumns[c] == LEFT_TOE) && + initialState->combinedColumns[c] != LEFT_TOE && + initialState->combinedColumns[c] != LEFT_HEEL) || + ((resultState->combinedColumns[c] == RIGHT_HEEL || + resultState->combinedColumns[c] == RIGHT_TOE) && + initialState->combinedColumns[c] != RIGHT_TOE && + initialState->combinedColumns[c] != RIGHT_HEEL)) { + int previousFoot =initialState->whereTheFeetAre[resultState->combinedColumns[c]]; cost += HOLDSWITCH * - (previousFoot == -1 + (previousFoot == INVALID_COLUMN ? 1 : sqrt( layout.getDistanceSq(c, previousFoot) @@ -220,7 +160,7 @@ float StepParityCost::calcBracketTapCost(State * initialState, State * resultSta { // Small penalty for trying to jack a bracket during a hold float cost = 0; - if (leftHeel != -1 && leftToe != -1) + if (leftHeel != INVALID_COLUMN && leftToe != INVALID_COLUMN) { float jackPenalty = 1; if ( @@ -240,7 +180,7 @@ float StepParityCost::calcBracketTapCost(State * initialState, State * resultSta } } - if (rightHeel != -1 && rightToe != -1) { + if (rightHeel != INVALID_COLUMN && rightToe != INVALID_COLUMN) { float jackPenalty = 1; if ( initialState->didTheFootMove[RIGHT_TOE] || @@ -271,7 +211,7 @@ float StepParityCost::calcMovingFootWhileOtherIsntOnPadCost(State * initialState { float cost = 0; // Weighting for moving a foot while the other isn't on the pad (so marked doublesteps are less bad than this) - if (std::any_of(initialState->columns.begin(), initialState->columns.end(), [](Foot elem) { return elem != NONE; })) + if (std::any_of(initialState->combinedColumns.begin(), initialState->combinedColumns.end(), [](Foot elem) { return elem != NONE; })) { for (auto f : resultState->movedFeet) { @@ -281,16 +221,16 @@ float StepParityCost::calcMovingFootWhileOtherIsntOnPadCost(State * initialState case LEFT_TOE: if ( !( - initialState->whereTheFeetAre[RIGHT_HEEL] != -1 || - initialState->whereTheFeetAre[RIGHT_TOE] != -1)) + initialState->whereTheFeetAre[RIGHT_HEEL] != INVALID_COLUMN || + initialState->whereTheFeetAre[RIGHT_TOE] != INVALID_COLUMN)) cost += OTHER; break; case RIGHT_HEEL: case RIGHT_TOE: if ( !( - initialState->whereTheFeetAre[LEFT_HEEL] != -1 || - initialState->whereTheFeetAre[LEFT_TOE] != -1)) + initialState->whereTheFeetAre[LEFT_HEEL] != INVALID_COLUMN || + initialState->whereTheFeetAre[LEFT_TOE] != INVALID_COLUMN)) cost += OTHER; break; default: @@ -383,17 +323,17 @@ float StepParityCost::calcSlowBracketCost(Row & row, bool movedLeft, bool movedR 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]; + int leftHeel = resultState->whatNoteTheFootIsHitting[LEFT_HEEL]; + int leftToe = resultState->whatNoteTheFootIsHitting[LEFT_TOE]; + int rightHeel = resultState->whatNoteTheFootIsHitting[RIGHT_HEEL]; + int rightToe = resultState->whatNoteTheFootIsHitting[RIGHT_TOE]; StagePoint leftPos = layout.averagePoint(leftHeel, leftToe); StagePoint rightPos = layout.averagePoint(rightHeel, rightToe); bool crossedOver = rightPos.x < leftPos.x; - bool rightBackwards = rightHeel != -1 && rightToe != -1 ? layout.columns[rightToe].y < layout.columns[rightHeel].y : false; - bool leftBackwards = leftHeel != -1 && leftToe != -1 ? layout.columns[leftToe].y < layout.columns[leftHeel].y : false; + bool rightBackwards = rightHeel != INVALID_COLUMN && rightToe != INVALID_COLUMN ? layout.columns[rightToe].y < layout.columns[rightHeel].y : false; + bool leftBackwards = leftHeel != INVALID_COLUMN && leftToe != INVALID_COLUMN ? layout.columns[leftToe].y < layout.columns[leftHeel].y : false; if(!crossedOver && (rightBackwards || leftBackwards)) { @@ -418,18 +358,18 @@ float StepParityCost::calcMissedFootswitchCost(Row & row, bool jackedLeft, bool return cost; } -float StepParityCost::calcFacingCosts(State * initialState, State * resultState, std::vector & combinedColumns, int columnCount) +float StepParityCost::calcFacingCosts(State * initialState, State * resultState, int columnCount) { float cost = 0; - float endLeftHeel = -1; - float endLeftToe = -1; - float endRightHeel = -1; - float endRightToe = -1; + int endLeftHeel = INVALID_COLUMN; + int endLeftToe = INVALID_COLUMN; + int endRightHeel = INVALID_COLUMN; + int endRightToe = INVALID_COLUMN; for (int i = 0; i < columnCount; i++) { - switch (combinedColumns[i]) { + switch (resultState->combinedColumns[i]) { case NONE: break; case LEFT_HEEL: @@ -448,24 +388,24 @@ float StepParityCost::calcFacingCosts(State * initialState, State * resultState, } } - if (endLeftToe == -1) endLeftToe = endLeftHeel; - if (endRightToe == -1) endRightToe = endRightHeel; + if (endLeftToe == INVALID_COLUMN) endLeftToe = endLeftHeel; + if (endRightToe == INVALID_COLUMN) endRightToe = endRightHeel; // facing backwards gives a bit of bad weight (scaled heavily the further back you angle, so crossovers aren't Too bad; less bad than doublesteps) float heelFacing = - endLeftHeel != -1 && endRightHeel != -1 + endLeftHeel != INVALID_COLUMN && endRightHeel != INVALID_COLUMN ? layout.getXDifference(endLeftHeel, endRightHeel) : 0; float toeFacing = - endLeftToe != -1 && endRightToe != -1 + endLeftToe != INVALID_COLUMN && endRightToe != INVALID_COLUMN ? layout.getXDifference(endLeftToe, endRightToe) : 0; float leftFacing = - endLeftHeel != -1 && endLeftToe != -1 + endLeftHeel != INVALID_COLUMN && endLeftToe != INVALID_COLUMN ? layout.getYDifference(endLeftHeel, endLeftToe) : 0; float rightFacing = - endRightHeel != -1 && endRightToe != -1 + endRightHeel != INVALID_COLUMN && endRightToe != INVALID_COLUMN ? layout.getYDifference(endRightHeel, endRightToe) : 0; @@ -488,17 +428,17 @@ float StepParityCost::calcFacingCosts(State * initialState, State * resultState, return cost; } -float StepParityCost::calcSpinCosts(State * initialState, State * resultState, std::vector & combinedColumns, int columnCount) +float StepParityCost::calcSpinCosts(State * initialState, State * resultState, int columnCount) { float cost = 0; - float endLeftHeel = -1; - float endLeftToe = -1; - float endRightHeel = -1; - float endRightToe = -1; + int endLeftHeel = INVALID_COLUMN; + int endLeftToe = INVALID_COLUMN; + int endRightHeel = INVALID_COLUMN; + int endRightToe = INVALID_COLUMN; for (int i = 0; i < columnCount; i++) { - switch (combinedColumns[i]) { + switch (resultState->combinedColumns[i]) { case NONE: break; case LEFT_HEEL: @@ -517,8 +457,8 @@ float StepParityCost::calcSpinCosts(State * initialState, State * resultState, s } } - if (endLeftToe == -1) endLeftToe = endLeftHeel; - if (endRightToe == -1) endRightToe = endRightHeel; + if (endLeftToe == INVALID_COLUMN) endLeftToe = endLeftHeel; + if (endRightToe == INVALID_COLUMN) endRightToe = endRightHeel; // spin StagePoint previousLeftPos = layout.averagePoint( @@ -553,7 +493,7 @@ float StepParityCost::calcSpinCosts(State * initialState, State * resultState, s // 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 StepParityCost::caclFootswitchCost(State * initialState, State * resultState, Row & row, float elapsedTime, int columnCount) { float cost = 0; if (elapsedTime >= SLOW_FOOTSWITCH_THRESHOLD && elapsedTime < SLOW_FOOTSWITCH_IGNORE) { @@ -569,13 +509,13 @@ float StepParityCost::caclFootswitchCost(State * initialState, State * resultSta for (int i = 0; i < columnCount; i++) { if ( - initialState->columns[i] == NONE || + initialState->combinedColumns[i] == NONE || resultState->columns[i] == NONE) continue; if ( - initialState->columns[i] != resultState->columns[i] && - initialState->columns[i] != OTHER_PART_OF_FOOT[resultState->columns[i]] + initialState->combinedColumns[i] != resultState->columns[i] && + initialState->combinedColumns[i] != OTHER_PART_OF_FOOT[resultState->columns[i]] ) { cost += (timeScaled / (SLOW_FOOTSWITCH_THRESHOLD + timeScaled)) * FOOTSWITCH; @@ -593,10 +533,10 @@ float StepParityCost::calcSideswitchCost(State * initialState, State * resultSta for(auto c : layout.sideArrows) { if ( - initialState->columns[c] != resultState->columns[c] && + initialState->combinedColumns[c] != resultState->columns[c] && resultState->columns[c] != NONE && - initialState->columns[c] != NONE && - !resultState->didTheFootMove[initialState->columns[c]]) + initialState->combinedColumns[c] != NONE && + !resultState->didTheFootMove[initialState->combinedColumns[c]]) { cost += SIDESWITCH; } @@ -631,19 +571,19 @@ float StepParityCost::calcBigMovementsQuicklyCost(State * initialState, State * } int initialPosition = initialState->whereTheFeetAre[foot]; - if(initialPosition == -1) + if(initialPosition == INVALID_COLUMN) { continue; } - int resultPosition = resultState->whereTheFeetAre[foot]; + int resultPosition = resultState->whatNoteTheFootIsHitting[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) + bool isBracketing = resultState->whatNoteTheFootIsHitting[OTHER_PART_OF_FOOT[foot]] != INVALID_COLUMN; + if(isBracketing && resultState->whatNoteTheFootIsHitting[OTHER_PART_OF_FOOT[foot]] == initialPosition) { continue; } @@ -667,21 +607,21 @@ float StepParityCost::calcCrowdedBracketCost(State * initialState, State * resul { float cost = 0; - bool resultLeftBracket = resultState->whereTheFeetAre[LEFT_HEEL] > -1 && resultState->whereTheFeetAre[LEFT_TOE] > -1; - bool resultRightBracket = resultState->whereTheFeetAre[RIGHT_HEEL] > -1 && resultState->whereTheFeetAre[RIGHT_TOE] > -1; + bool resultLeftBracket = resultState->whatNoteTheFootIsHitting[LEFT_HEEL] > INVALID_COLUMN && resultState->whatNoteTheFootIsHitting[LEFT_TOE] > INVALID_COLUMN; + bool resultRightBracket = resultState->whatNoteTheFootIsHitting[RIGHT_HEEL] > INVALID_COLUMN && resultState->whatNoteTheFootIsHitting[RIGHT_TOE] > INVALID_COLUMN; - bool initialLeftBracket = initialState->whereTheFeetAre[LEFT_HEEL] > -1 && initialState->whereTheFeetAre[LEFT_TOE] > -1; - bool initialRightBracket = initialState->whereTheFeetAre[RIGHT_HEEL] > -1 && initialState->whereTheFeetAre[RIGHT_TOE] > -1; + bool initialLeftBracket = initialState->whereTheFeetAre[LEFT_HEEL] > INVALID_COLUMN && initialState->whereTheFeetAre[LEFT_TOE] > INVALID_COLUMN; + bool initialRightBracket = initialState->whereTheFeetAre[RIGHT_HEEL] > INVALID_COLUMN && initialState->whereTheFeetAre[RIGHT_TOE] > INVALID_COLUMN; // if we're trying to bracket with left foot, does it overlap the right foot // in previous state? if( (resultLeftBracket) && ( - initialState->columns[resultState->whereTheFeetAre[LEFT_HEEL]] == RIGHT_HEEL || - initialState->columns[resultState->whereTheFeetAre[LEFT_HEEL]] == RIGHT_TOE || - initialState->columns[resultState->whereTheFeetAre[LEFT_TOE]] == RIGHT_HEEL || - initialState->columns[resultState->whereTheFeetAre[LEFT_TOE]] == RIGHT_TOE + initialState->combinedColumns[resultState->whatNoteTheFootIsHitting[LEFT_HEEL]] == RIGHT_HEEL || + initialState->combinedColumns[resultState->whatNoteTheFootIsHitting[LEFT_HEEL]] == RIGHT_TOE || + initialState->combinedColumns[resultState->whatNoteTheFootIsHitting[LEFT_TOE]] == RIGHT_HEEL || + initialState->combinedColumns[resultState->whatNoteTheFootIsHitting[LEFT_TOE]] == RIGHT_TOE ) ) { @@ -702,10 +642,10 @@ float StepParityCost::calcCrowdedBracketCost(State * initialState, State * resul // and if we're trying to bracket with right foot, does it overlap the left ? if((resultRightBracket ) && ( - initialState->columns[resultState->whereTheFeetAre[RIGHT_HEEL]] == LEFT_HEEL || - initialState->columns[resultState->whereTheFeetAre[RIGHT_HEEL]] == LEFT_TOE || - initialState->columns[resultState->whereTheFeetAre[RIGHT_TOE]] == LEFT_HEEL || - initialState->columns[resultState->whereTheFeetAre[RIGHT_TOE]] == LEFT_TOE + initialState->combinedColumns[resultState->whatNoteTheFootIsHitting[RIGHT_HEEL]] == LEFT_HEEL || + initialState->combinedColumns[resultState->whatNoteTheFootIsHitting[RIGHT_HEEL]] == LEFT_TOE || + initialState->combinedColumns[resultState->whatNoteTheFootIsHitting[RIGHT_TOE]] == LEFT_HEEL || + initialState->combinedColumns[resultState->whatNoteTheFootIsHitting[RIGHT_TOE]] == LEFT_TOE ) ) { @@ -778,8 +718,8 @@ bool StepParityCost::didJackLeft(State * initialState, State * resultState, int if(!didJump && movedLeft) { - if ( leftHeel > -1 && - initialState->columns[leftHeel] == LEFT_HEEL && + if ( leftHeel > INVALID_COLUMN && + initialState->combinedColumns[leftHeel] == LEFT_HEEL && !resultState->isTheFootHolding[LEFT_HEEL] && ((initialState->didTheFootMove[LEFT_HEEL] && !initialState->isTheFootHolding[LEFT_HEEL]) || @@ -789,8 +729,8 @@ bool StepParityCost::didJackLeft(State * initialState, State * resultState, int jackedLeft = true; } if ( - leftToe > -1 && - initialState->columns[leftToe] == LEFT_TOE && + leftToe > INVALID_COLUMN && + initialState->combinedColumns[leftToe] == LEFT_TOE && !resultState->isTheFootHolding[LEFT_TOE] && ((initialState->didTheFootMove[LEFT_HEEL] && !initialState->isTheFootHolding[LEFT_HEEL]) || @@ -809,8 +749,8 @@ bool StepParityCost::didJackRight(State * initialState, State * resultState, int bool jackedRight = false; if(!didJump && movedRight) { - if ( rightHeel > -1 && - initialState->columns[rightHeel] == RIGHT_HEEL && + if ( rightHeel > INVALID_COLUMN && + initialState->combinedColumns[rightHeel] == RIGHT_HEEL && !resultState->isTheFootHolding[RIGHT_HEEL] && ((initialState->didTheFootMove[RIGHT_HEEL] && !initialState->isTheFootHolding[RIGHT_HEEL]) || @@ -819,8 +759,8 @@ bool StepParityCost::didJackRight(State * initialState, State * resultState, int ) { jackedRight = true; } - if ( rightToe > -1 && - initialState->columns[rightToe] == RIGHT_TOE && + if ( rightToe > INVALID_COLUMN && + initialState->combinedColumns[rightToe] == RIGHT_TOE && !resultState->isTheFootHolding[RIGHT_TOE] && ((initialState->didTheFootMove[RIGHT_HEEL] && !initialState->isTheFootHolding[RIGHT_HEEL]) || diff --git a/src/StepParityCost.h b/src/StepParityCost.h index 04f46d16ee..700e010c2e 100644 --- a/src/StepParityCost.h +++ b/src/StepParityCost.h @@ -50,12 +50,11 @@ namespace StepParity /// @param rows /// @param rowIndex The index of the row represented by resultState /// @return The computed cost - float getActionCost(State * initialState, State * resultState, std::vector &rows, int rowIndex); + float getActionCost(State * initialState, State * resultState, std::vector &rows, int rowIndex, float elapsedTime); private: - void mergeInitialAndResultPosition(State * initialState, State * resultState, std::vector &combinedColumns, int columnCount); - float calcMineCost(State * initialState, State * resultState, Row &row, std::vector &combinedColumns, int columnCount); - float calcHoldSwitchCost(State * initialState, State * resultState, Row &row, std::vector &combinedColumns, int columnCount); + float calcMineCost(State * initialState, State * resultState, Row &row, int columnCount); + float calcHoldSwitchCost(State * initialState, State * resultState, Row &row, int columnCount); float calcBracketTapCost(State * initialState, State * resultState, Row &row, int leftHeel, int leftToe, int rightHeel, int rightToe, float elapsedTime, int columnCount); float calcMovingFootWhileOtherIsntOnPadCost(State * initialState, State * resultState, int columnCount); float calcBracketJackCost(State * initialState, State * resultState, std::vector &rows, int rowIndex, bool movedLeft, bool movedRight, bool jackedLeft, bool jackedRight, bool didJump, int columnCount); @@ -64,9 +63,9 @@ namespace StepParity 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); - float caclFootswitchCost(State * initialState, State * resultState, Row &row, std::vector &combinedColumns, float elapsedTime, int columnCount); + float calcFacingCosts(State * initialState, State * resultState, int columnCount); + float calcSpinCosts(State * initialState, State * resultState, int columnCount); + float caclFootswitchCost(State * initialState, State * resultState, Row &row, float elapsedTime, int columnCount); float calcSideswitchCost(State * initialState, State * resultState, int columnCount); float calcJackCost(bool movedLeft, bool movedRight, bool jackedLeft, bool jackedRight, float elapsedTime, int columnCount); float calcBigMovementsQuicklyCost(State * initialState, State * resultState, float elapsedTime, int columnCount); diff --git a/src/StepParityDatastructs.cpp b/src/StepParityDatastructs.cpp index 8c9e362bc7..8e6d889d02 100644 --- a/src/StepParityDatastructs.cpp +++ b/src/StepParityDatastructs.cpp @@ -3,74 +3,15 @@ using namespace StepParity; -// Graph/Node methods -int calculateVectorHash(const std::vector &vec) -{ - int value = 0; - for(Foot f : vec) - { - value *= 5; - value += f; - } - return value; -} bool State::operator==(const State &other) const { - return rowIndex == other.rowIndex && - columns == other.columns && + return columns == other.columns && + combinedColumns == other.combinedColumns && movedFeet == other.movedFeet && holdFeet == other.holdFeet; } -bool State::operator<(const State &other) const -{ - - if(rowIndex != other.rowIndex) { - return rowIndex < other.rowIndex; - } - if(columnsHash != other.columnsHash) { - return columnsHash < other.columnsHash; - } - if(movedFeetHash != other.movedFeetHash) { - return movedFeetHash < other.movedFeetHash; - } - if(holdFeetHash != other.holdFeetHash) { - return holdFeetHash < other.holdFeetHash; - } - return false; -} - -void State::calculateHashes() -{ - columnsHash = calculateVectorHash(columns); - movedFeetHash = calculateVectorHash(movedFeet); - holdFeetHash = calculateVectorHash(holdFeet); -} - -StepParityNode * StepParityGraph::addOrGetExistingNode(const State &state) -{ - // This is silly, but the start node has a rowIndex of -1 - // which doesn't work as an array index. - int rowIndex = state.rowIndex + 1; - while (static_cast(stateNodeMap.size()) <= rowIndex) - { - stateNodeMap.push_back(std::map()); - } - - if(stateNodeMap[rowIndex].find(state) == stateNodeMap[rowIndex].end()) - { - StepParityNode* newNode = new StepParityNode(state); - newNode->id = int(nodes.size()); - nodes.push_back(newNode); - newNode->state.idx = int(states.size()); - states.push_back(&(newNode->state)); - stateNodeMap[rowIndex][state] = newNode; - } - - return stateNodeMap[rowIndex][state]; -} - // StageLayout bool StageLayout::bracketCheck(int column1, int column2) { @@ -176,166 +117,22 @@ void Row::setFootPlacement(const std::vector & footPlacement) } } -// Json methods -template -Json::Value FeetToJson(const Container& feets, bool useStrings) +bool Row::operator==(const Row& other) const { - Json::Value root; - for(Foot f: feets) - { - if(useStrings) - { - root.append(FEET_LABELS[static_cast(f)]); - } - else - { - root.append(static_cast(f)); - } - } - return root; + return second == other.second && + beat == other.beat && + rowIndex == other.rowIndex && + columnCount == other.columnCount && + noteCount == other.noteCount && + holdTails == other.holdTails && + mines == other.mines && + fakeMines == other.fakeMines && + columns == other.columns && + whereTheFeetAre == other.whereTheFeetAre; + } -Json::Value State::ToJson(bool useStrings) +bool Row::operator!=(const Row& other) const { - Json::Value root; - Json::Value jsonColumns = FeetToJson(columns, useStrings); - Json::Value jsonMovedFeet = FeetToJson(movedFeet, useStrings); - Json::Value jsonHoldFeet = FeetToJson(holdFeet, useStrings); - - root["idx"] = idx; - root["columns"] = jsonColumns; - root["movedFeet"] = jsonMovedFeet; - root["holdFeet"] = jsonHoldFeet; - root["second"] = second; - root["rowIndex"] = rowIndex; - return root; -} - -Json::Value IntermediateNoteData::ToJson(bool useStrings) -{ - Json::Value root; - if(useStrings) - { - root["type"] = TapNoteTypeShortNames[static_cast(type)]; - root["subtype"] = TapNoteSubTypeShortNames[static_cast(subtype)]; - root["parity"] = FEET_LABELS[static_cast(parity)]; - - } - else - { - root["type"] = static_cast(type); - root["subtype"] = static_cast(subtype); - root["parity"] = static_cast(parity); - } - root["col"] = col; - root["row"] = row; - root["beat"] = beat; - root["hold"] = hold_length; - root["warped"] = warped; - root["fake"] = fake; - root["second"] = second; - return root; -} - -Json::Value Row::ToJson(bool useStrings) -{ - Json::Value root; - Json::Value jsonNotes; - Json::Value jsonHolds; - Json::Value jsonHoldTails; - Json::Value jsonMines; - Json::Value jsonFakeMines; - - for (IntermediateNoteData n : notes) { jsonNotes.append(n.ToJson(useStrings)); } - for (IntermediateNoteData n : holds) { jsonHolds.append(n.ToJson(useStrings)); } - for (int t : holdTails) { jsonHoldTails.append(t); } - for (int m : mines) { jsonMines.append(m); } - for (int f : fakeMines) { jsonFakeMines.append(f); } - - root["notes"] = jsonNotes; - root["holds"] = jsonHolds; - root["hold_tails"] = jsonHoldTails; - root["mines"] = jsonMines; - root["fake_mines"] = jsonFakeMines; - root["second"] = second; - root["beat"] = beat; - - return root; -} - -Json::Value StepParityNode::ToJson() -{ - Json::Value root; - Json::Value jsonNeighbors; - - for (auto it = neighbors.begin(); it != neighbors.end(); it++) - { - Json::Value n; - n["id"] = it->first->id; - Json::Value jsonCosts; - float cost = it->second; - n["cost"] = cost; - jsonNeighbors.append(n); - } - root["id"] = id; - root["stateIdx"] = state.idx; - root["neighbors"] = jsonNeighbors; - return root; -} - -Json::Value Row::ToJsonRows(const std::vector & rows, bool useStrings) -{ - Json::Value root; - for(Row row: rows) - { - root.append(row.ToJson(useStrings)); - } - return root; -} - -Json::Value Row::ParityRowsJson(const std::vector & rows) -{ - Json::Value root; - for(Row row: rows) - { - Json::Value pr; - for(IntermediateNoteData note: row.notes) - { - pr.append(static_cast(note.parity)); - } - root.append(pr); - } - return root; -} - -Json::Value StepParityGraph::ToJson() -{ - Json::Value jsonNodes; - for(auto node: nodes) - { - jsonNodes.append(node->ToJson()); - } - Json::Value jsonStates; - for(auto state: states) - { - jsonStates.append(state->ToJson(false)); - } - - Json::Value root; - root["nodes"] = jsonNodes; - root["states"] = jsonStates; - return root; -} - -Json::Value StepParityGraph::NodeStateJson() -{ - Json::Value root; - for(auto node: nodes) - { - Json::Value nodeJson; - nodeJson["id"] = node->id; - nodeJson["state"] = node->state.ToJson(false); - root.append(nodeJson); - } - return root; + return !operator==(other); } diff --git a/src/StepParityDatastructs.h b/src/StepParityDatastructs.h index dc8d7f33be..61a5de2b56 100644 --- a/src/StepParityDatastructs.h +++ b/src/StepParityDatastructs.h @@ -3,13 +3,12 @@ #include "GameConstantsAndTypes.h" #include "NoteData.h" -#include "json/json.h" -#include "JsonUtil.h" #include #include namespace StepParity { + const int INVALID_COLUMN = -1; const float CLM_SECOND_INVALID = -1; enum Foot @@ -119,49 +118,34 @@ namespace StepParity { /// @brief Represents a specific possible state of the player's position /// for a given row of the step chart. struct State { - FootPlacement columns; // The position of the player + FootPlacement columns; // what the feet are hitting on this row + FootPlacement combinedColumns; // The resulting position of the player FootPlacement movedFeet; // Any feet that have moved from the previous state to this one FootPlacement holdFeet; // Any feet that stayed in place due to a hold/roll note. - float second; // The time of the song represented by this state - int rowIndex; // The index of the row represented by this state - int idx; - int whereTheFeetAre[NUM_Foot]; // the inverse of columns + int whereTheFeetAre[NUM_Foot]; // the inverse of combinedColumns + int whatNoteTheFootIsHitting[NUM_Foot]; // the inverse of columns bool didTheFootMove[NUM_Foot]; // the inverse of movedFeet bool isTheFootHolding[NUM_Foot]; //inverse of holdFeet - // These hashes are used in operator<() to speed up the comparison of the vectors. - // Their values are computed by calculateHashes(), which is used in StepParityGenerator::buildStateGraph(). - int columnsHash = 0; - int movedFeetHash = 0; - int holdFeetHash = 0; - - State() - { - State(4); - } State(int columnCount) { columns = FootPlacement(columnCount, NONE); + combinedColumns = FootPlacement(columnCount, NONE); movedFeet = FootPlacement(columnCount, NONE); holdFeet = FootPlacement(columnCount, NONE); - second = 0; - rowIndex = 0; - idx = -1; + for(int i = 0; i < NUM_Foot; i++) { - whereTheFeetAre[i] = -1; + whatNoteTheFootIsHitting[i] = INVALID_COLUMN; + whereTheFeetAre[i] = INVALID_COLUMN; didTheFootMove[i] = false; isTheFootHolding[i] = false; } } - Json::Value ToJson(bool useStrings); - bool operator==(const State& other) const; - bool operator<(const State& other) const; - - void calculateHashes(); + bool operator==(const State& other) const; }; /// @brief A convenience struct used to encapsulate data from NoteData in an @@ -179,7 +163,6 @@ namespace StepParity { float second = false; // time into the song on which the note occurs Foot parity = NONE; // Which foot (and which part of the foot) will most likely be used - Json::Value ToJson(bool useStrings); }; @@ -223,14 +206,13 @@ namespace StepParity { mines = std::vector(columnCount, 0); fakeMines = std::vector(columnCount, 0); columns = std::vector(columnCount, StepParity::NONE); - whereTheFeetAre = std::vector(StepParity::NUM_Foot, -1); + whereTheFeetAre = std::vector(StepParity::NUM_Foot, INVALID_COLUMN); } void setFootPlacement(const std::vector & footPlacement); - - Json::Value ToJson(bool useStrings); - static Json::Value ToJsonRows(const std::vector & rows, bool useStrings); - static Json::Value ParityRowsJson(const std::vector & rows); + + bool operator==(const Row& other) const; + bool operator!=(const Row& other) const; }; /// @brief A counter used while creating rows @@ -279,8 +261,10 @@ namespace StepParity { { // The index of this node in its graph int id = 0; - State state; - + State * state; + int rowIndex = 0; + float second = 0; + // Connections to, and the cost of moving to, the connected nodes std::unordered_map neighbors; @@ -288,73 +272,18 @@ namespace StepParity { { neighbors.clear(); } - StepParityNode(const State &_state) + StepParityNode(State *_state, float _second, int _rowIndex) { state = _state; + rowIndex = _rowIndex; + second = _second; } - - Json::Value ToJson(); int neighborCount() { return static_cast(neighbors.size()); } }; - - /// @brief A comparator, needed in order use State objects as the key in a std::map. - struct StateComparator - { - bool operator()(const State& lhs, const State& rhs) const { - return lhs < rhs; - } - }; - - /// @brief A graph, representing all of the possible states for a step chart. - class StepParityGraph - { - private: - std::vector nodes; - std::vector states; - std::vector> stateNodeMap; - - public: - // This represents the very start of the song, before any notes - StepParityNode * startNode; - // This represents the end of the song, after all of the notes - StepParityNode *endNode; - - ~StepParityGraph() - { - states.clear(); - for(StepParityNode * node: nodes) - { - delete node; - } - } - - /// @brief Returns a pointer to a StepParityNode that represents the given state within the graph. - /// If a node already exists, it is returned, otherwise a new one is created and added to the graph. - /// @param state - /// @return - StepParityNode *addOrGetExistingNode(const State &state); - - void addEdge(StepParityNode* from, StepParityNode* to, float cost) - { - from->neighbors[to] = cost; - } - - int nodeCount() const - { - return static_cast(nodes.size()); - } - - Json::Value ToJson(); - Json::Value NodeStateJson(); - StepParityNode *operator[](int index) const - { - return nodes[index]; - } - }; }; #endif diff --git a/src/StepParityGenerator.cpp b/src/StepParityGenerator.cpp index d7bc8d48ae..f13d7b1672 100644 --- a/src/StepParityGenerator.cpp +++ b/src/StepParityGenerator.cpp @@ -28,8 +28,8 @@ void StepParityGenerator::analyzeGraph() { for (unsigned long i = 0; i < rows.size(); i++) { - StepParityNode *node = graph[nodes_for_rows[i]]; - rows[i].setFootPlacement(node->state.columns); + StepParityNode *node = nodes[nodes_for_rows[i]]; + rows[i].setFootPlacement(node->state->combinedColumns); } } @@ -37,87 +37,93 @@ void StepParityGenerator::buildStateGraph() { // The first node of the graph is beginningState, which represents the time before // the first note (and so it's roIndex is considered -1) - State beginningState(columnCount); - beginningState.rowIndex = -1; - beginningState.second = rows[0].second - 1; - StepParityNode *startNode = graph.addOrGetExistingNode(beginningState); - - graph.startNode = startNode; - - std::queue previousStates; - previousStates.push(beginningState); + beginningState = new State(columnCount); + startNode = addNode(beginningState, rows[0].second - 1, -1); + + std::queue previousNodes; + previousNodes.push(startNode); StepParityCost costCalculator(layout); for (unsigned long i = 0; i < rows.size(); i++) { - std::vector uniqueStates; + std::vector resultNodes; Row &row = rows[i]; std::vector *PermuteFootPlacements = getFootPlacementPermutations(row); - while (!previousStates.empty()) + while (!previousNodes.empty()) { - State state = previousStates.front(); - StepParityNode *initialNode = graph.addOrGetExistingNode(state); - + StepParityNode *initialNode = previousNodes.front(); + float elapsedTime = row.second - initialNode->second; for(auto it = PermuteFootPlacements->begin(); it != PermuteFootPlacements->end(); it++) { - State resultState = initResultState(state, row, *it); - float cost = costCalculator.getActionCost(&state, &resultState, rows, i); - resultState.calculateHashes(); - StepParityNode *resultNode = graph.addOrGetExistingNode(resultState); - graph.addEdge(initialNode, resultNode, cost); - if(std::find(uniqueStates.begin(), uniqueStates.end(), resultState) == uniqueStates.end()) - { - uniqueStates.push_back(resultState); - } + State * resultState = initResultState(initialNode->state, row, *it); + + float cost = costCalculator.getActionCost(initialNode->state, resultState, rows, i, elapsedTime); + addStateToGraph(resultState, initialNode, row, resultNodes, cost); + } - previousStates.pop(); + previousNodes.pop(); } - for (State s : uniqueStates) + for (StepParityNode * n : resultNodes) { - previousStates.push(s); + previousNodes.push(n); } } // at this point, previousStates holds all of the states for the very last row, // which just get connected to the endState - State endState(columnCount); - endState.rowIndex = rows.size(); - endState.second = rows[rows.size() - 1].second + 1; - StepParityNode *endNode = graph.addOrGetExistingNode(endState); - graph.endNode = endNode; - while(!previousStates.empty()) + endingState = new State(columnCount); + endNode = addNode(endingState, rows[rows.size() - 1].second + 1, rows.size()); + + while(!previousNodes.empty()) { - State state = previousStates.front(); - StepParityNode *node = graph.addOrGetExistingNode(state); - graph.addEdge(node, endNode, 0); - previousStates.pop(); + StepParityNode *node = previousNodes.front(); + addEdge(node, endNode, 0); + previousNodes.pop(); } } -State StepParityGenerator::initResultState(State &initialState, Row &row, const FootPlacement &columns) +void StepParityGenerator::addStateToGraph(State * resultState, StepParityNode * initialNode, Row & row, std::vector &existingNodesForThisRow, float cost) { - State resultState(row.columnCount); - resultState.columns = columns; - resultState.rowIndex = row.rowIndex; + + for(StepParityNode * existingNode : existingNodesForThisRow) + { + if(existingNode->state == resultState) + { + addEdge(initialNode, existingNode, cost); + return; + } + } + StepParityNode *resultNode = addNode(resultState, row.second, row.rowIndex); + addEdge(initialNode, resultNode, cost); + existingNodesForThisRow.push_back(resultNode); +} + + +State * StepParityGenerator::initResultState(State * initialState, Row &row, const FootPlacement &columns) +{ + State * resultState = new State(row.columnCount); + resultState->columns = columns; + + // I tried to condense this, but kept getting the logic messed up for (unsigned long i = 0; i < columns.size(); i++) { if(columns[i] == NONE) { continue; } - resultState.whereTheFeetAre[columns[i]] = i; + resultState->whatNoteTheFootIsHitting[columns[i]] = i; if(row.holds[i].type == TapNoteType_Empty) { - resultState.movedFeet[i] = columns[i]; - resultState.didTheFootMove[columns[i]] = true; + resultState->movedFeet[i] = columns[i]; + resultState->didTheFootMove[columns[i]] = true; continue; } - if(initialState.columns[i] != columns[i]) + if(initialState->combinedColumns[i] != columns[i]) { - resultState.movedFeet[i] = columns[i]; - resultState.didTheFootMove[columns[i]] = true; + resultState->movedFeet[i] = columns[i]; + resultState->didTheFootMove[columns[i]] = true; } } @@ -129,14 +135,87 @@ State StepParityGenerator::initResultState(State &initialState, Row &row, const if(row.holds[i].type != TapNoteType_Empty) { - resultState.holdFeet[i] = columns[i]; - resultState.isTheFootHolding[columns[i]] = true; + resultState->holdFeet[i] = columns[i]; + resultState->isTheFootHolding[columns[i]] = true; } } - resultState.second = row.second; + + mergeInitialAndResultPosition(initialState, resultState, (int)columns.size()); + + std::uint64_t stateHash = getStateCacheKey(resultState); + + auto maybeState = stateCache.find(stateHash); + + if(maybeState != stateCache.end()) + { + State* cachedState = maybeState->second; + delete resultState; + return maybeState->second; + } + + stateCache.insert({stateHash, resultState}); + return resultState; } +// This merges the `columns` properties of initialState and resultState, which +// fully represents the player's position on the dance stage. +// For example: +// initialState.combinedColumns = [L,0,0,R] +// resultState.columns = [0,L,0,0] +// combinedColumns = [0,L,0,R] +// This eventually gets saved back to resultState +void StepParityGenerator::mergeInitialAndResultPosition(State * initialState, State * resultState, int columnCount) +{ + // Merge initial + result position + for (int i = 0; i < columnCount; i++) { + // copy in data from resultState over the top which overrides it, as long as it's not nothing + if (resultState->columns[i] != NONE) { + resultState->combinedColumns[i] = resultState->columns[i]; + continue; + } + + // copy in data from initialState, if it wasn't moved + if ( + initialState->combinedColumns[i] == LEFT_HEEL || + initialState->combinedColumns[i] == RIGHT_HEEL + ) { + if (!resultState->didTheFootMove[initialState->combinedColumns[i]]) { + resultState->combinedColumns[i] = initialState->combinedColumns[i]; + } + } else if (initialState->combinedColumns[i] == LEFT_TOE) { + if ( + !resultState->didTheFootMove[LEFT_TOE] && + !resultState->didTheFootMove[LEFT_HEEL] + ) { + resultState->combinedColumns[i] = initialState->combinedColumns[i]; + } + } else if (initialState->combinedColumns[i] == RIGHT_TOE) { + if ( + !resultState->didTheFootMove[RIGHT_TOE] && + !resultState->didTheFootMove[RIGHT_HEEL] + ) { + resultState->combinedColumns[i] = initialState->combinedColumns[i]; + } + } + } + + for(int i = 0; i < columnCount; i++) + { + if(resultState->combinedColumns[i] != NONE) + { + resultState->whereTheFeetAre[resultState->combinedColumns[i]] = i; + } + } +} + +// For the given row, generate all of the possible foot placements +// (even if they're not physically possible) +// +// We cache this data and return a pointer for two reasons: +// - It takes a long time to generate the permutations +// - We end up generating a lot of redundant data, so caching it saves memory + std::vector* StepParityGenerator::getFootPlacementPermutations(const Row &row) { int cacheKey = getPermuteCacheKey(row); @@ -151,15 +230,18 @@ std::vector* StepParityGenerator::getFootPlacementPermutations(co return &permuteCache[cacheKey]; } - +// Recursively generate each permutation for the given row. std::vector StepParityGenerator::PermuteFootPlacements(const Row &row, FootPlacement columns, unsigned long column) { + // If column >= columns.size(), we've reached the end of the row. + // Perform some final validation before returning the contents of columns if (column >= columns.size()) { - int leftHeelIndex = -1; - int leftToeIndex = -1; - int rightHeelIndex = -1; - int rightToeIndex = -1; + int leftHeelIndex = StepParity::INVALID_COLUMN; + int leftToeIndex = StepParity::INVALID_COLUMN; + int rightHeelIndex = StepParity::INVALID_COLUMN; + int rightToeIndex = StepParity::INVALID_COLUMN; + for (unsigned long i = 0; i < columns.size(); i++) { if (columns[i] == NONE) @@ -183,20 +265,24 @@ std::vector StepParityGenerator::PermuteFootPlacements(const Row rightToeIndex = i; } } + + // Filter out actually invalid combinations: + // - We don't want permutations where the toe is on an arrow, but not the heel + // - We don't want impossible brackets (eg you can't bracket up and down) if ( - (leftHeelIndex == -1 && leftToeIndex != -1) || - (rightHeelIndex == -1 && rightToeIndex != -1)) + (leftHeelIndex == StepParity::INVALID_COLUMN && leftToeIndex != StepParity::INVALID_COLUMN) || + (rightHeelIndex == StepParity::INVALID_COLUMN && rightToeIndex != StepParity::INVALID_COLUMN)) { return std::vector(); } - if (leftHeelIndex != -1 && leftToeIndex != -1) + if (leftHeelIndex != StepParity::INVALID_COLUMN && leftToeIndex != StepParity::INVALID_COLUMN) { if (!layout.bracketCheck(leftHeelIndex, leftToeIndex)) { return std::vector(); } } - if (rightHeelIndex != -1 && rightToeIndex != -1) + if (rightHeelIndex != StepParity::INVALID_COLUMN && rightToeIndex != StepParity::INVALID_COLUMN) { if (!layout.bracketCheck(rightHeelIndex, rightToeIndex)) { @@ -206,11 +292,18 @@ std::vector StepParityGenerator::PermuteFootPlacements(const Row return {columns}; } + // If this column has a valid tap/hold head, or is actively holding a note, + // iterate through values of StepParity::Foot. For each foot part, check that + // it's not already present in columns, and if not, create a copy of columns, + // and set the current foot part to the current column. + // Then pass it to PermuteFootPlacements() and increment the column index. + // Collect each permutationm, and then return all of them. + std::vector permutations; if (row.notes[column].type != TapNoteType_Empty || row.holds[column].type != TapNoteType_Empty) { - for (StepParity::Foot foot: FEET) { + for (StepParity::Foot foot: FEET) { if(std::find(columns.begin(), columns.end(), foot) != columns.end()) { continue; @@ -221,24 +314,27 @@ std::vector StepParityGenerator::PermuteFootPlacements(const Row newColumns[column] = foot; std::vector p = PermuteFootPlacements(row, newColumns, column + 1); permutations.insert(permutations.end(), p.begin(), p.end()); - } - return permutations; + } + return permutations; } + // If the current column doesn't have any taps or holds, + // then we don't need to generate any permutations for it. + // Return the contents of calling PermuteFootPlacements() for the next column. return PermuteFootPlacements(row, columns, column + 1); } std::vector StepParityGenerator::computeCheapestPath() { - int start = graph.startNode->id; - int end = graph.endNode->id; + int start = startNode->id; + int end = endNode->id; std::vector shortest_path; - std::vector cost(graph.nodeCount(), FLT_MAX); - std::vector predecessor(graph.nodeCount(), -1); + std::vector cost(nodes.size(), FLT_MAX); + std::vector predecessor(nodes.size(), -1); cost[start] = 0; for (int i = start; i <= end; i++) { - StepParityNode *node = graph[i]; + StepParityNode *node = nodes[i]; for(auto neighbor: node->neighbors) { int neighbor_id = neighbor.first->id; @@ -472,15 +568,42 @@ int StepParityGenerator::getPermuteCacheKey(const Row &row) return key; } -Json::Value StepParityGenerator::SMEditorParityJson() +std::uint64_t StepParityGenerator::getStateCacheKey(State * state) { - Json::Value root; - - for (unsigned long i = 0; i < nodes_for_rows.size(); i++) + std::uint64_t value = 0; + const std::uint64_t prime = 31; + for(Foot f : state->columns) { - StepParityNode *node = graph[nodes_for_rows[i]]; - root.append(node->state.ToJson(false)); + value *= prime; + value += f; } - - return root; + for(Foot f : state->combinedColumns) + { + value *= prime; + value += f; + } + for(Foot f : state->movedFeet) + { + value *= prime; + value += f; + } + for(Foot f : state->holdFeet) + { + value *= prime; + value += f; + } + return value; +} + +StepParityNode * StepParityGenerator::addNode(State *state, float second, int rowIndex) +{ + StepParityNode * newNode = new StepParityNode(state, second, rowIndex); + newNode->id = int(nodes.size()); + nodes.push_back(newNode); + return newNode; +} + +void StepParityGenerator::addEdge(StepParityNode* from, StepParityNode* to, float cost) +{ + from->neighbors[to] = cost; } diff --git a/src/StepParityGenerator.h b/src/StepParityGenerator.h index b4ffd25d64..915d7a26d6 100644 --- a/src/StepParityGenerator.h +++ b/src/StepParityGenerator.h @@ -35,15 +35,42 @@ namespace StepParity { { private: StageLayout layout; - std::map < int, std::vector>> permuteCache; - + std::unordered_map < int, std::vector>> permuteCache; + std::unordered_map stateCache; + std::vector nodes; + StepParity::State * beginningState = nullptr; + StepParity::StepParityNode * startNode = nullptr; + StepParity::State * endingState = nullptr; + StepParity::StepParityNode * endNode = nullptr; + public: - StepParityGraph graph; std::vector rows; std::vector nodes_for_rows; int columnCount; StepParityGenerator(const StageLayout & l) : layout(l) { + + } + + ~StepParityGenerator() + { + for(auto s : stateCache) + { + delete s.second; + } + for(auto n: nodes) + { + delete n; + } + if(beginningState != nullptr) + { + delete beginningState; + } + + if(endingState != nullptr) + { + delete endingState; + } } /// @brief Analyzes the given NoteData to generate a vector of StepParity::Rows, with each step annotated with /// a foot placement. @@ -59,14 +86,17 @@ namespace StepParity { /// and one that represents the end of the song, after the final note. void buildStateGraph(); + void addStateToGraph(State * resultState, StepParityNode * initialNode, Row & row, std::vector &existingNodesForThisRow, float cost); /// @brief Creates a new State, which is the result of moving from the given initialState /// to the steps of the given row with the given foot placements in columns. /// @param initialState The state of the player prior to the next row /// @param row The next row for the resulting state /// @param columns The foot placement for the resulting state /// @return The resulting state - State initResultState(State &initialState, Row &row, const FootPlacement &columns); + State * initResultState(State * initialState, Row &row, const FootPlacement &columns); + void mergeInitialAndResultPosition(State * initialState, State * resultState, int columnCount); + /// @brief Returns a pointer to a vector of foot possible foot placements for the given row. /// Utilizes the permuteCache to re-use vectors. The returned pointer points to a vector within the permuteCache. /// @param row The row to calculate foot placement permutations for. @@ -97,9 +127,9 @@ namespace StepParity { void AddRow(RowCounter &counter); Row CreateRow(RowCounter &counter); int getPermuteCacheKey(const Row &row); - bool bracketCheck(int column1, int column2); - float getDistanceSq(StepParity::StagePoint p1, StepParity::StagePoint p2); - Json::Value SMEditorParityJson(); + std::uint64_t getStateCacheKey(State * state); + StepParityNode * addNode(State *state, float second, int rowIndex); + void addEdge(StepParityNode* from, StepParityNode* to, float cost); }; }; diff --git a/src/TechCounts.cpp b/src/TechCounts.cpp index 0c1617466a..102121a3a1 100644 --- a/src/TechCounts.cpp +++ b/src/TechCounts.cpp @@ -112,7 +112,7 @@ void TechCounts::CalculateTechCountsFromRows(const std::vector { for (StepParity::Foot foot: StepParity::FEET) { - if(currentRow.whereTheFeetAre[foot] == -1 || previousRow.whereTheFeetAre[foot] == -1) + if(currentRow.whereTheFeetAre[foot] == StepParity::INVALID_COLUMN || previousRow.whereTheFeetAre[foot] == StepParity::INVALID_COLUMN) { continue; } @@ -137,12 +137,12 @@ void TechCounts::CalculateTechCountsFromRows(const std::vector // Check for brackets if(currentRow.noteCount >= 2) { - if(currentRow.whereTheFeetAre[StepParity::LEFT_HEEL] != -1 && currentRow.whereTheFeetAre[StepParity::LEFT_TOE] != -1) + if(currentRow.whereTheFeetAre[StepParity::LEFT_HEEL] != StepParity::INVALID_COLUMN && currentRow.whereTheFeetAre[StepParity::LEFT_TOE] != StepParity::INVALID_COLUMN) { out[TechCountsCategory_Brackets] += 1; } - if(currentRow.whereTheFeetAre[StepParity::RIGHT_HEEL] != -1 && currentRow.whereTheFeetAre[StepParity::RIGHT_TOE] != -1) + if(currentRow.whereTheFeetAre[StepParity::RIGHT_HEEL] != StepParity::INVALID_COLUMN && currentRow.whereTheFeetAre[StepParity::RIGHT_TOE] != StepParity::INVALID_COLUMN) { out[TechCountsCategory_Brackets] += 1; } @@ -197,7 +197,7 @@ void TechCounts::CalculateTechCountsFromRows(const std::vector // - Was the right foot farther right than the left? // - If so, then this was a full crossover (like RDL, starting on right foot) // - otherwise, then this was probably a half crossover (like UDL, starting on right foot) - if(rightHeel != -1 && previousLeftHeel != -1 && previousRightHeel == -1) + if(rightHeel != StepParity::INVALID_COLUMN && previousLeftHeel != StepParity::INVALID_COLUMN && previousRightHeel == StepParity::INVALID_COLUMN) { StepParity::StagePoint leftPos = layout.averagePoint(previousLeftHeel, previousLeftToe); StepParity::StagePoint rightPos = layout.averagePoint(rightHeel, rightToe); @@ -209,7 +209,7 @@ void TechCounts::CalculateTechCountsFromRows(const std::vector const StepParity::Row & previousPreviousRow = rows[i - 2]; int previousPreviousRightHeel = previousPreviousRow.whereTheFeetAre[StepParity::RIGHT_HEEL]; - if(previousPreviousRightHeel != -1 && previousPreviousRightHeel != rightHeel) + if(previousPreviousRightHeel != StepParity::INVALID_COLUMN && previousPreviousRightHeel != rightHeel) { StepParity::StagePoint previousPreviousRightPos = layout.columns[previousPreviousRightHeel]; if(previousPreviousRightPos.x > leftPos.x) @@ -231,7 +231,7 @@ void TechCounts::CalculateTechCountsFromRows(const std::vector } } // And check the same thing, starting with left foot - else if(leftHeel != -1 && previousRightHeel != -1 && previousLeftHeel == -1) + else if(leftHeel != StepParity::INVALID_COLUMN && previousRightHeel != StepParity::INVALID_COLUMN && previousLeftHeel == StepParity::INVALID_COLUMN) { StepParity::StagePoint leftPos = layout.averagePoint(leftHeel, leftToe); StepParity::StagePoint rightPos = layout.averagePoint(previousRightHeel, previousRightToe); @@ -241,9 +241,10 @@ void TechCounts::CalculateTechCountsFromRows(const std::vector if(i > 1) { const StepParity::Row & previousPreviousRow = rows[i - 2]; - if(previousPreviousRow.whereTheFeetAre[StepParity::LEFT_HEEL] != leftHeel) + int previousPreviousLeftHeel = previousPreviousRow.whereTheFeetAre[StepParity::LEFT_HEEL]; + if(previousPreviousLeftHeel != StepParity::INVALID_COLUMN && previousPreviousLeftHeel != leftHeel) { - StepParity::StagePoint previousPreviousLeftPos = layout.columns[previousPreviousRow.whereTheFeetAre[StepParity::LEFT_HEEL]]; + StepParity::StagePoint previousPreviousLeftPos = layout.columns[previousPreviousLeftHeel]; if(rightPos.x > previousPreviousLeftPos.x) { out[TechCountsCategory_FullCrossovers] += 1;