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')
This commit is contained in:
+199
-76
@@ -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<State> previousStates;
|
||||
previousStates.push(beginningState);
|
||||
beginningState = new State(columnCount);
|
||||
startNode = addNode(beginningState, rows[0].second - 1, -1);
|
||||
|
||||
std::queue<StepParityNode *> previousNodes;
|
||||
previousNodes.push(startNode);
|
||||
StepParityCost costCalculator(layout);
|
||||
|
||||
for (unsigned long i = 0; i < rows.size(); i++)
|
||||
{
|
||||
std::vector<State> uniqueStates;
|
||||
std::vector<StepParityNode *> resultNodes;
|
||||
Row &row = rows[i];
|
||||
std::vector<FootPlacement> *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<StepParityNode *> &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<FootPlacement>* StepParityGenerator::getFootPlacementPermutations(const Row &row)
|
||||
{
|
||||
int cacheKey = getPermuteCacheKey(row);
|
||||
@@ -151,15 +230,18 @@ std::vector<FootPlacement>* StepParityGenerator::getFootPlacementPermutations(co
|
||||
return &permuteCache[cacheKey];
|
||||
}
|
||||
|
||||
|
||||
// Recursively generate each permutation for the given row.
|
||||
std::vector<FootPlacement> 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<FootPlacement> 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<FootPlacement>();
|
||||
}
|
||||
if (leftHeelIndex != -1 && leftToeIndex != -1)
|
||||
if (leftHeelIndex != StepParity::INVALID_COLUMN && leftToeIndex != StepParity::INVALID_COLUMN)
|
||||
{
|
||||
if (!layout.bracketCheck(leftHeelIndex, leftToeIndex))
|
||||
{
|
||||
return std::vector<FootPlacement>();
|
||||
}
|
||||
}
|
||||
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<FootPlacement> 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<FootPlacement> 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<FootPlacement> StepParityGenerator::PermuteFootPlacements(const Row
|
||||
newColumns[column] = foot;
|
||||
std::vector<FootPlacement> 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<int> StepParityGenerator::computeCheapestPath()
|
||||
{
|
||||
int start = graph.startNode->id;
|
||||
int end = graph.endNode->id;
|
||||
int start = startNode->id;
|
||||
int end = endNode->id;
|
||||
std::vector<int> shortest_path;
|
||||
std::vector<float> cost(graph.nodeCount(), FLT_MAX);
|
||||
std::vector<int> predecessor(graph.nodeCount(), -1);
|
||||
std::vector<float> cost(nodes.size(), FLT_MAX);
|
||||
std::vector<int> 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user