platform-pixels-level-generator
Version:
A command line tool used to generate levels
412 lines (348 loc) • 10.4 kB
JavaScript
"use strict";
require('colors');
var constants = require('./constants');
var pathUtil = require('path');
var Square = require('./Square');
var r = require('./random');
var gm = require('gm');
class Grid {
constructor (w, h) {
this.width = w;
this.height = h;
this._grid = null;
this._state = null;
this.initialize(new Square(constants.TYPE_FILL));
}
/**
* Initialize a the grid with a given square
* @param s
*/
initialize (s) {
this._grid = [];
for (var y = 0; y < this.height; y++) {
var col = [];
for (var x = 0; x < this.width; x++) {
col.push(s);
}
this._grid.push(col);
}
}
/**
* Add a grid to this one.
*
* @param grid Grid to add
* @param offsetX Offset the grid by x
* @param offsetY Offset the grid by y
*/
add (grid, offsetX, offsetY) {
offsetX = offsetX || 0;
offsetY = offsetY || 0;
for (var y = 0; y < grid.height; y++) {
for (var x = 0; x < grid.width; x++) {
this.set(
grid.get(x, y),
x + offsetX, y + offsetY
);
}
}
}
/**
* Set a square
*
* @param t
* @param x
* @param y
*/
set (s, x, y) {
x = x === undefined ? this._posX : x;
y = y === undefined ? this._posY : y;
//console.log('SET:', t, x, y);
if (y >= this.height || y < 0 || x >= this.width || x < 0) {
return;
}
this._grid[this.height - y - 1][x] = s;
}
/**
* Set a random empty space to the given square
*
* @param s
* @param options
*/
setRandom (s, options) {
options = options || {};
var yMax = options.yMax || 1E9;
var yMin = options.yMin || -1E9;
var possibilities = [];
for (var y = this.height - 1; y >= 0.; y--) {
for (var x = 0; x < this.width; x++) {
if (y <= yMax && y >= yMin && this.get(x, y).is(constants.TYPE_EMPTY)) {
possibilities.push([x, y]);
}
}
}
var coords = r.choice(possibilities);
this.set(s, coords[0], coords[1]);
}
/**
* Get the type at a given coordinate
*
* @param x
* @param y
* @returns Square
*/
get (x, y) {
if (y >= this.height || y < 0 || x >= this.width || x < 0) {
return null;
}
return this._grid[this.height - y - 1][x];
}
/**
* Clear a rectangle
*
* @param x1
* @param y1
* @param x2
* @param y2
* @returns {*}
*/
clear (x1, y1, x2, y2) {
return this.fill(
new Square(constants.TYPE_EMPTY),
x1,
y1,
x2,
y2
);
}
/**
* Fill a rectangle
*
* @param s
* @param x1
* @param y1
* @param x2
* @param y2
*/
fill (s, x1, y1, x2, y2) {
//console.log('Fill:', t, x1, y1, ' --> ', x2, y2);
var xMin = Math.min(x1, x2);
var xMax = Math.max(x1, x2);
var yMin = Math.min(y1, y2);
var yMax = Math.max(y1, y2);
for (var x = xMin; x <= xMax; x++) {
for (var y = yMin; y <= yMax; y++) {
this.set(s, x, y);
}
}
}
/**
* Find the exit to a grid
*
* @returns {{floor: number, ceil: number height: number}}
*/
findExit () {
var floor = null;
var ceil = null;
var turn = false;
var s;
for (let y = 0; y < this.height; y++) {
s = this.get(this.width - 1, y);
if (floor === null && s.is(constants.TYPE_EMPTY)) {
floor = y - 1;
} else if (floor !== null && s.isnt(constants.TYPE_EMPTY)) {
ceil = y;
break;
}
}
if (ceil === null && floor === null) {
console.log("Looking on left");
// We didn't find an exit on the right. Check left side.
let firstEntrance = this.findEntrance();
if (firstEntrance === null) {
return null;
}
let exit = this.findEntrance(firstEntrance.ceil + 1);
floor = exit.floor;
ceil = exit.ceil;
turn = true;
}
return {floor: floor, ceil: ceil, height: ceil - floor - 1, turn: turn};
}
/**
* Find the entrance to a grid
*
* @returns {{floor: number, ceil: number height: number}}
*/
findEntrance (startY) {
startY = startY || 0;
var floor = null;
var ceil = null;
var s;
for (let y = startY; y < this.height; y++) {
s = this.get(0, y);
if (floor === null && s.is(constants.TYPE_EMPTY)) {
floor = y - 1;
} else if (floor !== null && s.isnt(constants.TYPE_EMPTY)) {
ceil = y;
break;
}
}
if (floor === null || ceil === null) {
return null;
}
return {floor: floor, ceil: ceil, height: ceil - floor - 1, turn: false};
}
/**
* Crop the grid, leaving a certain amount of padding behind
*
* returns new grid instance
*
* @param pTop
* @param pRight
* @param pBottom
* @param pLeft
*/
crop (pTop, pRight, pBottom, pLeft) {
var yFirst = this.height - 1;
var yLast = 0;
var xFirst = this.width - 1;
var xLast = 0;
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
if (this.get(x, y).isnt(constants.TYPE_FILL)) {
xFirst = Math.min(xFirst, x);
yFirst = Math.min(yFirst, y);
}
}
}
for (let y = this.height - 1; y >= 0; y--) {
for (let x = this.width - 1; x >= 0; x--) {
if (this.get(x, y).isnt(constants.TYPE_FILL)) {
xLast = Math.max(xLast, x);
yLast = Math.max(yLast, y);
}
}
}
var oldGrid = this.clone();
this.width = xLast - xFirst + pLeft + pRight + 1;
this.height = yLast - yFirst + pTop + pBottom + 1;
this.initialize(new Square(constants.TYPE_FILL));
this.add(
oldGrid,
-xFirst + pLeft,
-yFirst + pBottom
);
}
/**
* Clone the grid instance
*/
clone () {
var newGrid = new Grid(this.width, this.height);
newGrid.add(this);
return newGrid;
}
/**
* Print the grid in ascii
*/
print () {
console.log('--');
var r, s;
var numbers = '\\ ';
for (let x = 0; x < this.width; x++) {
numbers += ' ' + (x % 10);
}
console.log(numbers + ' /\n');
for (let y = this.height - 1; y >= 0.; y--) {
r = (y % 10) + ' ';
for (let x = 0; x < this.width; x++) {
r += this.get(x, y).type + ' ';
}
r += ' ' + (y % 10);
console.log(r);
}
numbers = '/ ';
for (let x = 0; x < this.width; x++) {
numbers += ' ' + (x % 10);
}
console.log('\n' + numbers + ' \\');
console.log('--');
}
/**
* Export the grid to an image
*
* @param path
*/
saveImage (path) {
var img_visual = gm(this.width, this.height, 'transparent');
var img_meta = gm(this.width, this.height, 'transparent');
var s, c, sNext;
var startX, nextType;
for (var y = this.height - 1; y >= 0; y--) {
startX = 0;
for (var x = 0; x < this.width; x++) {
s = this.get(x, y);
sNext = this.get(x + 1, y);
nextType = sNext === null ? 'not-a-real-type' : sNext.type;
if (s.is(nextType)) {
continue;
}
c = constants.COLORS_META[s.type];
if (c) {
//console.log('META', startX, this.height - y - 1, x, this.height - y - 1, t);
img_meta.fill(c);
if (startX == x) {
img_meta.drawPoint(
startX,
this.height - y - 1
);
} else {
img_meta.drawRectangle(
startX,
this.height - y - 1,
x,
this.height - y - 1
);
}
}
c = constants.COLORS_VISUAL[s.type];
if (c) {
//console.log('VISU', startX, this.height - y - 1, x, this.height - y - 1, t);
img_visual.fill(c);
if (startX == x) {
img_visual.drawPoint(
startX,
this.height - y - 1
);
} else {
img_visual.drawRectangle(
startX,
this.height - y - 1,
x,
this.height - y - 1
);
}
}
startX = x + 1;
}
}
var metaPath = pathUtil.join(path, 'meta.png');
img_meta.write(metaPath, function (err) {
if (err) {
console.error('Failed to generate ' + metaPath, err);
}
else {
//console.log('Generated ', metaPath);
}
});
var visualPath = pathUtil.join(path, 'visual.png');
img_visual.write(visualPath, function (err) {
if (err) {
console.error('Failed to generate ' + visualPath, err);
}
else {
//console.log('Generated ', visualPath);
}
});
}
}
module.exports = Grid;