zden
Version:
A tool to create random maps for battlecode 2016
527 lines (467 loc) • 15.1 kB
JavaScript
var constants = require('./constants');
var xml = require('xml');
var fs = require('fs');
var path = require('path');
function isValid(x, y, height, width) {
return x >= 0 && y >= 0 && x < width && y < height;
}
function getDistance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
function validDenLoc(x, y, aArchonsLoc, bArchonLocs) {
// Each zombie den will be at least 30% closer (in terms of radiusSquared)
// to one team's closest archon than to the other team's closest archon.
var i;
var dist;
var closestA = 10000000;
var closestB = 10000000;
for (i = 0; i < aArchonsLoc.length; i++) {
dist = getDistance(x, y, aArchonsLoc[i].x, aArchonsLoc[i].y);
if (dist < closestA) {
closestA = dist
}
dist = getDistance(x, y, bArchonLocs[i].x, bArchonLocs[i].y);
if (dist < closestB) {
closestB = dist
}
}
// must be at least 5 units different between archons
if (Math.abs(closestA - closestB) < 5) {
return false;
}
// only have dens very close to archon spawns 10% of the time
if (Math.min(closestA, closestB) < 6 && Math.random() < .9) {
return false;
}
if (closestA < closestB) {
if (1.3 * closestA <= closestB) {
return true;
}
} else if (closestB < closestA) {
if (1.3 * closestB <= closestA) {
return true;
}
}
return false;
}
function yAxis(x, y, height, width) {
return !(y < (height / 2));
}
function yAxisMirrorCell(x, y, height, width, mirrored) {
if (y < height / 2) {
y = height - y - 1;
}
if (mirrored) {
x = width - x - 1;
}
return [[x, y]];
}
function xAxis(x, y, height, width) {
return !(x < (width / 2));
}
function xAxisMirrorCell(x, y, height, width, mirrored) {
if (x < width / 2) {
x = width - x - 1;
}
if (mirrored) {
y = height - y - 1;
}
return [[x, y]];
}
function diag(x, y, height, width) {
return (y > x);
}
function diagMirrorCell(x, y, height, width, mirrored) {
if (mirrored) {
return reverseDiagMirrorCell(y, x, height, width, false);
}
return [[y, x]];
}
function reverseDiag(x, y, height, width) {
return !(x + y < height);
}
function reverseDiagMirrorCell(x, y, height, width, mirrored) {
if (mirrored) {
return diagMirrorCell(height - y - 1, width - x - 1, height, width, false);
}
return [[height - y - 1, width - x - 1]];
}
function Map(width, height, symmetry, mirrored) {
this.map = [];
for (var i = 0; i < width; i++) {
this.map.push([]);
for (var j = 0; j < height; j++) {
this.map[i].push({
"rubble": 0,
"parts": 0,
"unit": null,
"team": null
});
}
}
this.xMin = Math.floor(500 * Math.random());
this.yMin = Math.floor(500 * Math.random());
this.width = width;
this.height = height;
this.isMirror = xAxis;
this.mirrorCellLoc = xAxisMirrorCell;
this.mirrored = mirrored;
if (symmetry === 1) {
this.isMirror = yAxis;
this.mirrorCellLoc = yAxisMirrorCell;
} else if (symmetry === 2 && width === height) {
this.isMirror = diag;
this.mirrorCellLoc = diagMirrorCell;
} else if (symmetry === 3 && width === height) {
this.isMirror = reverseDiag;
this.mirrorCellLoc = reverseDiagMirrorCell;
}
}
Map.prototype.validLoc = function(x, y) {
return isValid(x, y, this.height, this.width) && !this.isMirror(x, y, this.height, this.width, this.mirrored);
};
Map.prototype.pack = function(prop) {
var packed = [];
for (var i = 0; i < this.height; i++) {
var list = [];
for (var j = 0; j < this.width; j++) {
list.push(Math.min(Math.round(this.map[j][i][prop]), 1000000)); // max of 1000000 parts/rubble
}
packed.push({
"double-array": list.join(',')
});
}
return packed;
};
Map.prototype.placeNeutral = function(x, y, unit) {
if (x === null || y === null) {
var loc = this.getValidXY();
x = loc.x;
y = loc.y;
}
if (this.map[x][y].unit !== null) {
return;
}
this.placeUnitAtLoc(x, y, unit, constants.team.NEUTRAL, constants.team.NEUTRAL);
};
Map.prototype.placeUnitAtLoc = function(x, y, unit, team1, team2) {
this.map[x][y].unit = unit;
this.map[x][y].team = team1;
var mirror = this.mirrorCellLoc(x, y, this.height, this.width, this.mirrored);
mirror = mirror[0];
this.map[mirror[0]][mirror[1]].unit = unit;
this.map[mirror[0]][mirror[1]].team = team2;
return {
"x": mirror[0],
"y": mirror[1]
}
};
Map.prototype.addPartsToArea = function(x, y, val, solid) {
var dx = Math.floor(Math.random() * 3);
if (x === null || y == null) {
var loc = this.getValidXY();
x = loc.x;
y = loc.y;
}
for (var i = -dx; i <= dx; i++) {
for (var j = -dx; j <= dx; j++) {
if (!solid && Math.random() < .3) {
continue;
}
if (this.validLoc(x + i, y + j)) {
this.placeAtLoc(x + i, y + j, 'parts', val, true)
}
}
}
};
Map.prototype.placeAtLoc = function(x, y, type, val, replace) {
if (replace) {
this.map[x][y][type] = val;
} else {
this.map[x][y][type] += val;
}
var mirror = this.mirrorCellLoc(x, y, this.height, this.width, this.mirrored);
mirror = mirror[0];
if (replace) {
this.map[mirror[0]][mirror[1]][type] = val;
} else {
this.map[mirror[0]][mirror[1]][type] += val;
}
return [{
"x": mirror[0],
"y": mirror[1]
}]
};
Map.prototype.placeDens = function(n, archonLocs) {
var x, y, done;
var mirror;
var idx;
var denLocs = [];
for (var i = 0; i < n; i++) {
done = false;
idx = 0;
while(!done) {
idx += 1;
if (idx > 100) {
throw Error("Couldn't place den!");
}
var point = this.getValidXY();
x = point.x;
y = point.y;
if (!this.isMirror(x, y, this.height, this.width, this.mirrored)) {
if (!this.map[x][y].unit) {
if (validDenLoc(x, y, archonLocs.A, archonLocs.B)) {
done = true;
}
}
}
}
mirror = this.placeUnitAtLoc(x, y, constants.ZOMBIEDEN, constants.team.ZOMBIE, constants.team.ZOMBIE);
denLocs.push({"x": x, "y": y});
denLocs.push(mirror);
}
return denLocs;
};
Map.prototype.placeArchons = function(n, grouped) {
var aArchons = [];
var bArchons = [];
var mirror;
// if we aren't grouping archons, 25% of the time mix in blue with red
var mixed = !grouped && Math.random() < .25;
for (var i = 0; i < n; i++) {
var done = false;
var x, y;
while (!done) {
var point = this.getValidXY();
x = point.x;
y = point.y;
if (!this.isMirror(x, y, this.height, this.width, this.mirrored)) {
mirror = this.mirrorCellLoc(x, y, this.height, this.width, this.mirrored);
mirror = mirror[0];
if (x != mirror[0] || y != mirror[1]) {
done = true;
}
}
}
if (mixed && Math.random() < .5) {
mirror = this.placeUnitAtLoc(x, y, constants.units.ARCHON, constants.team.B, constants.team.A);
} else {
mirror = this.placeUnitAtLoc(x, y, constants.units.ARCHON, constants.team.A, constants.team.B);
}
aArchons.push({"x": x, "y": y});
bArchons.push(mirror);
var placed = 1;
while (grouped && placed < n) {
var dx = Math.round((Math.random() -.5) * 10);
var dy = Math.round((Math.random() -.5) * 10);
if (isValid(x + dx, y + dy, this.height, this.width)) {
mirror = this.mirrorCellLoc(x, y, this.height, this.width, this.mirrored);
mirror = mirror[0];
if (x != mirror[0] || y != mirror[1]) {
mirror = this.placeUnitAtLoc(x + dx, y + dy, constants.units.ARCHON, constants.team.A, constants.team.B);
aArchons.push({"x": x + dx, "y": y + dy});
bArchons.push(mirror);
placed +=1;
}
}
}
if (grouped) {
break;
}
}
return {
'A': aArchons,
'B': bArchons
}
};
Map.prototype.getValidXY = function() {
var x,y;
var done = false;
while (!done) {
x = Math.floor(Math.random() * this.width);
y = Math.floor(Math.random() * this.height);
if (this.validLoc(x, y)) {
done = true;
}
}
return {
"x": x,
"y": y
}
};
Map.prototype.cleanArchonSpawn = function(archonLocs) {
for (var idx = 0; idx < archonLocs.length; idx++) {
var loc = archonLocs[idx];
for (var i = -1; i <= 1; i++) {
for (var j = -1; j <= 1; j++) {
if (this.validLoc(loc.x + i, loc.y + j)) {
this.placeAtLoc(loc.x + i, loc.y + j, 'rubble', 0, true);
}
}
}
}
};
Map.prototype.layBlocks = function(n, weight, splotch, patchy, checkboard) {
for (var idx = 0; idx < n; idx++) {
var done = false;
while(!done) {
var point = this.getValidXY();
var dx = Math.floor(Math.random() * 10) + 2;
var dy = Math.floor(Math.random() * 10) + 2;
if (this.validLoc(point.x + dx, point.y + dy)) {
done = true;
}
}
for (var i = point.x; i <= point.x + dx; i++) {
for (var j = point.y; j <= point.y + dy; j++) {
if (checkboard && (i + j) % 2 == 0 ) {
continue;
}
if (patchy && Math.random() < .3) {
continue;
}
if (splotch) {
this.placeAtLoc(i, j, 'rubble', weight * (Math.random() + .5));
} else {
this.placeAtLoc(i, j, 'rubble', weight);
}
}
}
}
};
Map.prototype.decorateDens = function(dens) {
for (var idx = 0; idx < dens.length; idx ++) {
var den = dens[idx];
if (this.isMirror(den.x, den.y, this.height, this.width, this.mirrored)) {
continue;
}
for (var i = -3; i <= 3; i++) {
for (var j = -3; j <= 3; j++) {
if (Math.random() < .7 && this.validLoc(den.x + i, den.y + j)) {
this.placeAtLoc(den.x + i, den.y + j, 'rubble', Math.floor(Math.random() * 50));
}
}
}
}
};
Map.prototype.layWalls = function(n, weight, maxLength, splotch) {
var dx, dy;
for (var idx = 0; idx < n; idx++) {
var done = false;
var horiz = Math.random() > .5;
while(!done) {
var point = this.getValidXY();
var d = Math.floor(Math.random() * 8) + 2;
if (horiz) {
dx = d;
dy = 0;
} else {
dx = 0;
dy = d;
}
if (this.validLoc(point.x + dx, point.y + dy)) {
done = true;
}
}
for (var i = point.x; i <= point.x + dx; i++) {
for (var j = point.y; j <= point.y + dy; j++) {
if (splotch) {
this.placeAtLoc(i, j, 'rubble', weight * (Math.random() + .5));
} else {
this.placeAtLoc(i, j, 'rubble', weight * (Math.random() + .5));
}
}
}
}
};
Map.prototype.getZombieSchedule = function(zombieLevel) {
var schedule = constants.zombieSpawn[zombieLevel];
var sch = [];
var rounds = Object.keys(schedule);
for (var i = 0; i < rounds.length; i++) {
var r = schedule[rounds[i]];
var spawns = Object.keys(r);
var spws = [{"_attr": {number: rounds[i]}}];
for (var j = 0; j < spawns.length; j++) {
spws.push({
"zombie-count": [{
"_attr": {
type: spawns[j],
count: r[spawns[j]]
}
}]
})
}
sch.push({
"round": spws
})
}
return sch;
};
Map.prototype.getInitialRobots = function() {
var bots = [];
for (var i = 0; i < this.width; i++) {
for (var j = 0; j < this.height; j++) {
if (this.map[i][j].unit) {
bots.push({
"initial-robot": [{
"_attr": {
"originOffsetY": j,
"originOffsetX": i,
"type": this.map[i][j].unit,
"team": this.map[i][j].team
}
}]
})
}
}
}
return bots;
};
Map.prototype.getRubbleLevel = function() {
var rubbleTotal = 0;
for (var i = 0; i < this.width; i++) {
for (var j = 0; j < this.height; j++) {
rubbleTotal += this.map[i][j].rubble;
}
}
var averageRubble = rubbleTotal / (this.width * this.height);
return Math.max(0, Math.floor(10 * Math.log(averageRubble) / Math.log(constants.MAX_RUBBLE +.001)))
};
Map.prototype.toJSON = function(name, zombieLevel) {
return {
"object-stream": [
{
"game-map": [
{
"_attr": {
"width": this.width,
"height": this.height,
"origin": this.xMin + "," + this.yMin,
"seed": Math.round(Math.random() * 1000),
"rounds": 3000,
"mapName": name
}
},
{
"initialRubble": this.pack('rubble')
},
{
"initialParts": this.pack('parts')
},
{
"zombieSpawnSchedule": this.getZombieSchedule(zombieLevel)
},
{
"initialRobots": this.getInitialRobots()
}
]
},
]
};
};
Map.prototype.save = function(name, dir, zombieLevel) {
var mapString = xml(this.toJSON(name, zombieLevel), true);
fs.writeFileSync(path.join(dir, name + '.xml'), mapString);
};
module.exports = Map;