Skip to content

Commit

Permalink
fix(ui): Fix map coloring (#567)
Browse files Browse the repository at this point in the history
* Fix map coloring

Use segment ID from metadata instead of generated range.
Rename some methods and variables.
Colors are now assigned correctly, but we now apparently need five colors for some maps.

* Incorporate fifth color into default color array.

* Improve coloring algorithm

Sort vertices by number of edges in descending order before coloring.

* Empty map handling

Return default value instead of Error when given an empty map.
Update jsdoc to match the changed parameter.

* Increase layer scan accuracy

Change the resolution used to scan the given layer data.
The previous value leads to invalid coloring with some maps.

* Fix map iteration

Iterate the full map instead of just half of it.

* Decrease accuracy

Set resolution back to 6 as it was not the problem after all.
  • Loading branch information
aa-ko authored Jul 26, 2020
1 parent f14f6cc commit 890120c
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 48 deletions.
98 changes: 54 additions & 44 deletions client/zone/js-modules/map-color-finder.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,29 @@ export class FourColorTheoremSolver {
*/
constructor(layers, resolution) {
const prec = Math.floor(resolution);
this.stepFunction = function(c) {
this.stepFunction = function (c) {
return c + prec;
};
var preparedLayers = this.preprocessLayers(layers);
var map = this.createPixelToSegmentMapping(preparedLayers);
this.areaGraph = this.buildGraph(map);
this.areaGraph.colorAllVertices();
if (preparedLayers !== undefined) {
var mapData = this.createPixelToSegmentMapping(preparedLayers);
this.areaGraph = this.buildGraph(mapData);
this.areaGraph.colorAllVertices();
}
}

/**
* @param {number} segmentIndex - Zero-based index of the segment you want to get the color for.
* Segments are in the same order they had when they were passed into the class constructor.
* @param {number} segmentId - ID of the segment you want to get the color for.
* The segment ID is extracted from the layer meta data in the first contructor parameter of this class.
* @returns {number} The segment color, represented as an integer. Starts at 0 and goes up the minimal number of colors required to color the map without collisions.
*/
getColor(segmentIndex) {
var segmentFromGraph = this.areaGraph.getById(segmentIndex);
getColor(segmentId) {
if (this.areaGraph === undefined) {
// Layer preprocessing seems to have failed. Just return a default value for any input.
return 0;
}

var segmentFromGraph = this.areaGraph.getById(segmentId);
if (segmentFromGraph) {
return segmentFromGraph.color;
} else {
Expand All @@ -36,30 +43,32 @@ export class FourColorTheoremSolver {
}

preprocessLayers(layers) {
var segmentCounter = 0;
var internalSegments = [];
var boundaries = {
minX: Infinity,
maxX: -Infinity,
minY: Infinity,
maxY: -Infinity
};
layers.filter(layer => layer.type === "segment")
.forEach(layer => {
var allPixels = [];
for (let index = 0; index < layer.pixels.length / 2; index += 2) {
var p = {
x: layer.pixels[index],
y: layer.pixels[index + 1]
};
this.setBoundaries(boundaries, p);
allPixels.push(p);
}
internalSegments.push({
segmentIndex: segmentCounter++,
pixels: allPixels
});
const filteredLayers = layers.filter(layer => layer.type === "segment");
if (filteredLayers.length <= 0) {
return undefined;
}
filteredLayers.forEach(layer => {
var allPixels = [];
for (let index = 0; index < layer.pixels.length - 1; index += 2) {
var p = {
x: layer.pixels[index],
y: layer.pixels[index + 1]
};
this.setBoundaries(boundaries, p);
allPixels.push(p);
}
internalSegments.push({
segmentId: layer.metaData.segmentId,
pixels: allPixels
});
});
return {
boundaries: boundaries,
segments: internalSegments
Expand All @@ -86,22 +95,22 @@ export class FourColorTheoremSolver {
preparedLayers.boundaries.maxX + 1,
preparedLayers.boundaries.maxY + 1
);
var numberOfSegments = 0;
var segmentIds = [];
preparedLayers.segments.forEach(seg => {
numberOfSegments += 1;
segmentIds.push(seg.segmentId);
seg.pixels.forEach(p => {
pixelData[p.x][p.y] = seg.segmentIndex;
pixelData[p.x][p.y] = seg.segmentId;
});
});
return {
map: pixelData,
numberOfSegments: numberOfSegments,
segmentIds: segmentIds,
boundaries: preparedLayers.boundaries
};
}

buildGraph(mapData) {
var vertices = this.range(mapData.numberOfSegments).map(i => new MapAreaVertex(i));
var vertices = mapData.segmentIds.map(i => new MapAreaVertex(i));
var graph = new MapAreaGraph(vertices);
this.traverseMap(mapData.boundaries, mapData.map, (x, y, currentSegmentId, pixelData) => {
var newSegmentId = pixelData[x][y];
Expand Down Expand Up @@ -142,10 +151,6 @@ export class FourColorTheoremSolver {
}
return arr;
}

range(n) {
return Array.apply(null, { length: n }).map(Number.call, Number);
}
}

class MapAreaVertex {
Expand Down Expand Up @@ -184,19 +189,24 @@ class MapAreaGraph {

/**
* Color the graphs vertices using a greedy algorithm. Any vertices that have already been assigned a color will not be changed.
* Color assignment will start with the vertex that is connected with the highest number of edges. In most cases, this will
* naturally lead to a distribution where only four colors are required for the whole graph. This is relevant for maps with a high
* number of segments, as the naive, greedy algorithm tends to require a fifth color when starting coloring in a segment far from the map's center.
*
*/
colorAllVertices() {
this.vertices.forEach(v => {
if (v.adjacentVertexIds.size <= 0) {
v.color = 0;
} else {
var adjs = this.getAdjacentVertices(v);
var existingColors = adjs
.filter(vert => vert.color !== undefined)
.map(vert => vert.color);
v.color = this.lowestColor(existingColors);
}
});
this.vertices.sort((l, r) => r.adjacentVertexIds.size - l.adjacentVertexIds.size)
.forEach(v => {
if (v.adjacentVertexIds.size <= 0) {
v.color = 0;
} else {
var adjs = this.getAdjacentVertices(v);
var existingColors = adjs
.filter(vert => vert.color !== undefined)
.map(vert => vert.color);
v.color = this.lowestColor(existingColors);
}
});
}

getAdjacentVertices(vertex) {
Expand Down
8 changes: 4 additions & 4 deletions client/zone/js-modules/map-drawer.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export function MapDrawer() {
"#19A1A1",
"#7AC037",
"#DF5618",
"#F7C841"
"#F7C841",
"#9966CC" // "fallback" color
].map(function (e) {
return hexToRgb(e);
});
Expand All @@ -56,14 +57,13 @@ export function MapDrawer() {
color = occupiedColor;
break;
case "segment":
color = segmentColors[colorFinder.getColor((layer.metaData.segmentId - 1))];
color = segmentColors[colorFinder.getColor((layer.metaData.segmentId))];
alpha = 192;
break;
}


if (!color) {
console.error("Missing color for " + layer.type);
console.error(`Missing color for ${layer.type} with segment id '${layer.metaData.segmentId}'.`);
color = {r: 0, g: 0, b: 0};
}

Expand Down

0 comments on commit 890120c

Please sign in to comment.