|
#include <iostream> |
|
#include <queue> |
|
#include <array> |
|
#include <set> |
|
#include <vector> |
|
#include <algorithm> |
|
#include <initializer_list> |
|
|
|
constexpr int GRID_LENGTH = 5; |
|
constexpr int GRID_SIZE = GRID_LENGTH * GRID_LENGTH; |
|
constexpr int PRUNE_PERCENTILE = 75; |
|
enum BuildingType { |
|
EMPTY = 0, |
|
BLUE = 1, |
|
RED = 4, |
|
GREEN = 9, |
|
YELLOW = 16 |
|
}; |
|
|
|
#define garry std::array<BuildingType, GRID_SIZE> |
|
|
|
struct Node { |
|
BuildingType grid[GRID_SIZE] = { EMPTY }; |
|
}; |
|
|
|
int getBuildingIndex(int x, int y) { |
|
if (x < 0 || x >= GRID_LENGTH || y < 0 || y >= GRID_LENGTH) { |
|
return -1; |
|
} |
|
return x + y * GRID_LENGTH; |
|
} |
|
|
|
int getBuildingX(int index) { |
|
return index % GRID_LENGTH; |
|
} |
|
|
|
int getBuildingY(int index) { |
|
return index / GRID_LENGTH; |
|
} |
|
|
|
bool isValidLayout (BuildingType* grid) { |
|
for (int i = 0; i < GRID_SIZE; i++) { |
|
int x = getBuildingX(i); |
|
int y = getBuildingY(i); |
|
|
|
int neighbors[4] = { |
|
getBuildingIndex(x, y - 1), |
|
getBuildingIndex(x - 1, y), |
|
getBuildingIndex(x + 1, y), |
|
getBuildingIndex(x, y + 1) |
|
}; |
|
|
|
int emptyCount = 0; |
|
int blueFlag = 0; |
|
int redFlag = 0; |
|
int greenFlag = 0; |
|
|
|
for (int j = 0; j < 4; j++) { |
|
if (neighbors[j] == -1) { |
|
continue; |
|
} |
|
|
|
BuildingType buildingType = grid[neighbors[j]]; |
|
if (buildingType == EMPTY) { |
|
emptyCount++; |
|
} else if (buildingType == BLUE) { |
|
blueFlag = 1; |
|
} else if (buildingType == RED) { |
|
redFlag = 1; |
|
} else if (buildingType == GREEN) { |
|
greenFlag = 1; |
|
} |
|
} |
|
|
|
if (grid[i] == RED && blueFlag + emptyCount < 1) { |
|
return false; |
|
} |
|
if (grid[i] == GREEN && blueFlag + redFlag + emptyCount < 2) { |
|
return false; |
|
} |
|
if (grid[i] == YELLOW && blueFlag + redFlag + greenFlag + emptyCount < 3) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
int calculateScore(const BuildingType* grid) { |
|
int score = 0; |
|
for (int i = 0; i < GRID_SIZE; i++) { |
|
score += grid[i]; |
|
} |
|
return score; |
|
} |
|
|
|
garry identity(const garry& grid) { |
|
return grid; |
|
} |
|
|
|
garry rotate90(const garry& grid) { |
|
garry newGrid; |
|
for (int y = 0; y < GRID_LENGTH; y++) { |
|
for (int x = 0; x < GRID_LENGTH; x++) { |
|
newGrid[getBuildingIndex(GRID_LENGTH - y - 1, x)] = grid[getBuildingIndex(x, y)]; |
|
} |
|
} |
|
return newGrid; |
|
} |
|
|
|
garry rotate180(const garry& grid) { |
|
return rotate90(rotate90(grid)); |
|
} |
|
|
|
garry rotate270(const garry& grid) { |
|
return rotate90(rotate180(grid)); |
|
} |
|
|
|
garry reflHoriz(const garry& grid) { |
|
garry newGrid; |
|
for (int y = 0; y < GRID_LENGTH; y++) { |
|
for (int x = 0; x < GRID_LENGTH; x++) { |
|
newGrid[getBuildingIndex(x, GRID_LENGTH - y - 1)] = grid[getBuildingIndex(x, y)]; |
|
} |
|
} |
|
return newGrid; |
|
} |
|
|
|
garry reflVert(const garry& grid) { |
|
garry newGrid; |
|
for (int y = 0; y < GRID_LENGTH; y++) { |
|
for (int x = 0; x < GRID_LENGTH; x++) { |
|
newGrid[getBuildingIndex(GRID_LENGTH - x - 1, y)] = grid[getBuildingIndex(x, y)]; |
|
} |
|
} |
|
return newGrid; |
|
} |
|
|
|
// y=x |
|
garry reflDiagYeX(const garry& grid) { |
|
garry newGrid; |
|
for (int y = 0; y < GRID_LENGTH; y++) { |
|
for (int x = 0; x < GRID_LENGTH; x++) { |
|
newGrid[getBuildingIndex(y, x)] = grid[getBuildingIndex(x, y)]; |
|
} |
|
} |
|
return newGrid; |
|
} |
|
|
|
// y=-x |
|
garry reflDiagYeNX(const garry& grid) { |
|
garry newGrid; |
|
for (int y = 0; y < GRID_LENGTH; y++) { |
|
for (int x = 0; x < GRID_LENGTH; x++) { |
|
newGrid[getBuildingIndex(GRID_LENGTH - y - 1, GRID_LENGTH - x - 1)] = grid[getBuildingIndex(x, y)]; |
|
} |
|
} |
|
return newGrid; |
|
} |
|
|
|
garry (*transformations[8])(const garry&) = { |
|
identity, |
|
rotate90, |
|
rotate180, |
|
rotate270, |
|
reflHoriz, |
|
reflVert, |
|
reflDiagYeX, |
|
reflDiagYeNX |
|
}; |
|
|
|
// Also includes symmetrical layouts |
|
bool isDuplicateLayout(const BuildingType newGrid[], std::set<garry>& seenLayouts) { |
|
garry layout; |
|
std::copy(newGrid, newGrid + GRID_SIZE, layout.begin()); |
|
|
|
for (int i = 0; i < 8; i++) { |
|
layout = transformations[i](layout); |
|
if (seenLayouts.find(layout) != seenLayouts.end()) { |
|
return true; |
|
} |
|
} |
|
|
|
// No transformations matched, add to set |
|
seenLayouts.insert(layout); |
|
return false; |
|
} |
|
|
|
int main() { |
|
std::queue<Node> queue; |
|
std::set<garry> finalLayouts; |
|
std::set<garry> seenLayouts; |
|
Node initialNode; |
|
int buildingCount = 0; |
|
|
|
queue.push(initialNode); |
|
|
|
while (!queue.empty()) { |
|
Node currentNode = queue.front(); |
|
queue.pop(); |
|
|
|
int index = 0; |
|
while (currentNode.grid[index] != EMPTY || index == GRID_SIZE) { |
|
index++; |
|
} |
|
|
|
if (buildingCount < index) { |
|
buildingCount = index; |
|
std::cout << "Building " << buildingCount << "..." << std::endl; |
|
|
|
// Prune 75% of the lowest scorers every row after the first, to maximize score (so that all blue layouts don't ruin our performance) |
|
if (buildingCount % GRID_LENGTH == 0 && buildingCount / GRID_LENGTH > 1) { |
|
int nodeCount = queue.size(); |
|
std::vector<int> scores; |
|
|
|
// Collect scores from the queue |
|
for (int i = 0; i < nodeCount; i++) { |
|
Node node = queue.front(); |
|
queue.pop(); |
|
scores.push_back(calculateScore(node.grid)); |
|
queue.push(node); |
|
} |
|
|
|
// Sort scores to find the 75th percentile |
|
std::sort(scores.begin(), scores.end()); |
|
int percentileIndex = (nodeCount * PRUNE_PERCENTILE) / 100; |
|
int thresholdScore = scores[percentileIndex]; |
|
|
|
// Prune nodes below the 75th percentile score |
|
for (int i = 0; i < nodeCount; i++) { |
|
Node node = queue.front(); |
|
queue.pop(); |
|
if (calculateScore(node.grid) > thresholdScore) { |
|
queue.push(node); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Grid is full |
|
if (index == GRID_SIZE) { |
|
continue; |
|
} |
|
|
|
for (BuildingType building : {BLUE, RED, GREEN, YELLOW}) { |
|
Node newNode = currentNode; |
|
newNode.grid[index] = building; |
|
|
|
if (!isValidLayout(newNode.grid) || isDuplicateLayout(newNode.grid, seenLayouts)) { |
|
continue; |
|
} |
|
|
|
if (index == GRID_SIZE - 1) { |
|
garry newGrid; |
|
std::copy(newNode.grid, newNode.grid + GRID_SIZE, newGrid.begin()); |
|
finalLayouts.insert(newGrid); |
|
} else { |
|
queue.push(newNode); |
|
} |
|
|
|
} |
|
} |
|
|
|
// Loop through final layouts and find the one with the highest score |
|
int maxScore = 0; |
|
garry bestLayout; |
|
for (const garry& layout : finalLayouts) { |
|
int score = calculateScore(layout.begin()); |
|
if (score > maxScore) { |
|
maxScore = score; |
|
bestLayout = layout; |
|
} |
|
} |
|
|
|
// Print best layout |
|
for (int y = 0; y < GRID_LENGTH; y++) { |
|
for (int x = 0; x < GRID_LENGTH; x++) { |
|
std::cout << bestLayout[getBuildingIndex(x, y)] << " "; |
|
} |
|
std::cout << std::endl; |
|
} |
|
std::cout << "Score: " << maxScore << std::endl; |
|
|
|
return 0; |
|
} |