Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 219 additions & 0 deletions spec/engine/processor/intents/spawns/bornCreepSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
const _ = require('lodash'),
utils = require('../../../../../src/utils'),
driver = utils.getDriver(),
C = driver.constants,
movement = require('../../../../../src/processor/intents/movement'),
bornCreep = require('../../../../../src/processor/intents/spawns/_born-creep'),
roomsEnv = require('../../../../helpers/mocks/rooms'),
creepsEnv = require('../../../../helpers/mocks/creeps'),
spawnsEnv = require('../../../../helpers/mocks/spawns'),
users = require('../../../../helpers/mocks/users'),
intents = require('../../../../helpers/mocks/intents'),
common = require('../../../../helpers/mocks/common');

const OWNER = users.defaultId;
const HOSTILE = 'hostile-user-id';
const SPAWN_X = 16;
const SPAWN_Y = 1;

function neighbor(direction) {
const [dx, dy] = utils.getOffsetsByDirection(direction);
return { x: SPAWN_X + dx, y: SPAWN_Y + dy };
}

function createSpawningCreep(spawn, data) {
return creepsEnv.createCreep('scout', _.merge({
name: 'newborn',
user: spawn.user,
x: spawn.x,
y: spawn.y,
spawning: true
}, data));
}

function createHostile(direction, data) {
const pos = neighbor(direction);
return creepsEnv.createCreep('scout', _.merge({
name: 'invader',
user: HOSTILE,
x: pos.x,
y: pos.y,
ageTime: 1000,
_ticksToLive: 1500,
store: {}
}, data));
}

function createFriendly(direction, data) {
const pos = neighbor(direction);
return creepsEnv.createCreep('scout', _.merge({
name: 'blocker',
user: OWNER,
x: pos.x,
y: pos.y
}, data));
}

function createExtension(direction) {
const pos = neighbor(direction);
const extension = {
_id: common.generateId(),
type: 'extension',
room: 'E2S7',
user: OWNER,
x: pos.x,
y: pos.y,
hits: 1000,
hitsMax: 1000,
store: { energy: 0 }
};
intents.scope.roomObjects[extension._id] = extension;
intents.scope.bulk.insert(extension);
return extension;
}

describe('_born-creep', () => {
let scope, spawn, spawningCreep;

beforeEach(() => {
scope = intents.scope;
intents.reset();
scope.gameTime = 100;
scope.roomTerrain = roomsEnv.terrain.E2S7;
scope.eventLog = [];
scope.stats = { inc() {} };
scope.roomController = {
type: 'controller',
user: OWNER,
level: 8,
x: 20,
y: 20,
safeMode: 0
};

spawn = spawnsEnv.createSpawn({
x: SPAWN_X,
y: SPAWN_Y,
user: OWNER,
spawning: {
name: 'newborn',
needTime: 3,
spawnTime: 103,
directions: [1, 2, 3, 4, 5, 6, 7, 8]
}
});
spawningCreep = createSpawningCreep(spawn);
movement.init(scope.roomObjects, scope.roomTerrain);
});

it('spawns onto the first open preferred direction', () => {
const result = bornCreep(spawn, spawningCreep, scope);

const expected = neighbor(1);
expect(result).toBe(true);
expect(spawningCreep.spawning).toBe(false);
expect(spawningCreep.x).toBe(expected.x);
expect(spawningCreep.y).toBe(expected.y);
});

it('delays birth when friendly creeps block all preferred exits', () => {
[1, 2, 3, 4, 5, 6, 7, 8].forEach(d => createFriendly(d, { name: 'friend' + d }));
movement.init(scope.roomObjects, scope.roomTerrain);

const result = bornCreep(spawn, spawningCreep, scope);

expect(result).toBe(false);
expect(spawningCreep.spawning).toBe(true);
expect(spawningCreep.x).toBe(SPAWN_X);
expect(spawningCreep.y).toBe(SPAWN_Y);
});

it('spawnstomps a hostile creep when fully surrounded', () => {
const hostiles = [1, 2, 3, 4, 5, 6, 7, 8].map(d =>
createHostile(d, { name: 'invader' + d }));
movement.init(scope.roomObjects, scope.roomTerrain);

const result = bornCreep(spawn, spawningCreep, scope);

const stomped = hostiles[0];
expect(result).toBe(true);
expect(spawningCreep.spawning).toBe(false);
expect(spawningCreep.x).toBe(stomped.x);
expect(spawningCreep.y).toBe(stomped.y);
expect(scope.roomObjects[stomped._id]).toBeUndefined();
});

it('does not spawnstomp when a non-preferred direction is open', () => {
spawn.spawning.directions = [1];
const hostile = createHostile(1);
movement.init(scope.roomObjects, scope.roomTerrain);

const result = bornCreep(spawn, spawningCreep, scope);

expect(result).toBe(false);
expect(spawningCreep.spawning).toBe(true);
expect(scope.roomObjects[hostile._id]).toBe(hostile);
});

it('under safemode ignores hostile creeps and uses the preferred direction', () => {
scope.roomController.safeMode = scope.gameTime + 5000;
spawn.spawning.directions = [1];
const hostile = createHostile(1);
movement.init(scope.roomObjects, scope.roomTerrain);

const result = bornCreep(spawn, spawningCreep, scope);

const expected = neighbor(1);
expect(result).toBe(true);
expect(spawningCreep.spawning).toBe(false);
expect(spawningCreep.x).toBe(expected.x);
expect(spawningCreep.y).toBe(expected.y);
expect(scope.roomObjects[hostile._id]).toBe(hostile);
});

it('under safemode does not spawnstomp when surrounded by hostiles', () => {
scope.roomController.safeMode = scope.gameTime + 5000;
const hostiles = [1, 2, 3, 4, 5, 6, 7, 8].map(d =>
createHostile(d, { name: 'invader' + d }));
movement.init(scope.roomObjects, scope.roomTerrain);

const result = bornCreep(spawn, spawningCreep, scope);

const expected = neighbor(1);
expect(result).toBe(true);
expect(spawningCreep.spawning).toBe(false);
expect(spawningCreep.x).toBe(expected.x);
expect(spawningCreep.y).toBe(expected.y);
hostiles.forEach(hostile => {
expect(scope.roomObjects[hostile._id]).toBe(hostile);
});
});

it('under safemode still treats friendly creeps as birth obstacles', () => {
scope.roomController.safeMode = scope.gameTime + 5000;
spawn.spawning.directions = [1];
createFriendly(1);
movement.init(scope.roomObjects, scope.roomTerrain);

const result = bornCreep(spawn, spawningCreep, scope);

expect(result).toBe(false);
expect(spawningCreep.spawning).toBe(true);
expect(spawningCreep.x).toBe(SPAWN_X);
expect(spawningCreep.y).toBe(SPAWN_Y);
});

it('under safemode still treats structures as birth obstacles', () => {
scope.roomController.safeMode = scope.gameTime + 5000;
spawn.spawning.directions = [1];
createExtension(1);
movement.init(scope.roomObjects, scope.roomTerrain);

const result = bornCreep(spawn, spawningCreep, scope);

expect(result).toBe(false);
expect(spawningCreep.spawning).toBe(true);
expect(spawningCreep.x).toBe(SPAWN_X);
expect(spawningCreep.y).toBe(SPAWN_Y);
});
});
29 changes: 29 additions & 0 deletions spec/helpers/mocks/spawns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const _ = require('lodash'),
intents = require('./intents'),
common = require('./common'),
users = require('./users');

const commonData = {
type: 'spawn',
room: 'E2S7',
user: users.defaultId,
name: 'Spawn1',
store: { energy: 300 },
storeCapacityResource: { energy: 300 },
hits: 5000,
hitsMax: 5000,
spawning: null,
toString: function(){ return `[spawn #${this._id}@${this.x},${this.y}]`; }
};

exports.createSpawn = function(data) {
const copy = _.merge(
_.cloneDeep(commonData),
data);
if(!copy._id) {
copy._id = common.generateId();
}
intents.scope.roomObjects[copy._id] = copy;
intents.scope.bulk.insert(copy);
return copy;
};
27 changes: 1 addition & 26 deletions src/processor/intents/movement.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,6 @@ function canMove(object) {
(!!object._pulled || !object._oldFatigue && _.some(object.body, i => i.hits > 0 && i.type == C.MOVE));
}

function checkObstacleAtXY(x,y,object, roomIsInSafeMode) {
var hasObstacle = false, hasRoad = false;
_.forEach(roomObjects, (i) => {
if (i.x != x || i.y != y) {
return;
}
if ((i.type == 'creep' || i.type == 'powerCreep') && !objects[i._id] && (!roomIsInSafeMode || roomIsInSafeMode != object.user || roomIsInSafeMode == object.user && object.user == i.user) ||
i.type != 'creep' && i.type != 'powerCreep' && _.contains(C.OBSTACLE_OBJECT_TYPES, i.type) ||
i.type == 'rampart' && !i.isPublic && i.user != object.user ||
i.type == 'constructionSite' && i.user == object.user && _.contains(C.OBSTACLE_OBJECT_TYPES,
i.structureType)) {
hasObstacle = true;
return false;
}
if(i.type == 'road') {
hasRoad = true;
}
});
if(hasObstacle) {
return true;
}
return utils.checkTerrain(roomTerrain, x, y, C.TERRAIN_MASK_WALL) && !hasRoad;

}

function calcResourcesWeight(creep) {
var totalCarry = _.sum(creep.store), weight = 0;
for(var i = creep.body.length-1; i >= 0; i--) {
Expand Down Expand Up @@ -180,7 +155,7 @@ exports.check = function(roomIsInSafeMode) {
}
}

if(!canMove(object) || !!checkObstacleAtXY(x,y, object, roomIsInSafeMode)) {
if(!canMove(object) || !!utils.checkObstacleAtXY(x, y, object, roomIsInSafeMode, roomObjects, roomTerrain, objects)) {
removeFromMatrix(i);
}
}
Expand Down
23 changes: 9 additions & 14 deletions src/processor/intents/spawns/_born-creep.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
var _ = require('lodash'),
utils = require('../../../utils'),
driver = utils.getDriver(),
C = driver.constants,
movement = require('../movement');

module.exports = function(spawn, creep, scope) {

const {roomObjects, roomTerrain, bulk} = scope;
const {roomObjects, roomTerrain, bulk, roomController, gameTime} = scope;

const roomIsInSafeMode = roomController && roomController.safeMode > gameTime ? roomController.user : false;
const mySafeMode = roomIsInSafeMode && roomIsInSafeMode == spawn.user;

var newX, newY, isOccupied, hostileOccupied;
var checkObstacleFn = (i) => (i.x == newX && i.y == newY) && (
_.contains(C.OBSTACLE_OBJECT_TYPES, i.type) || // just unwalkable
(i.type == 'constructionSite' && _.contains(C.OBSTACLE_OBJECT_TYPES, i.structureType)) // unwalkable site
);

var directions = [1,2,3,4,5,6,7,8];
if(spawn.spawning && spawn.spawning.directions) {
Expand All @@ -25,16 +22,15 @@ module.exports = function(spawn, creep, scope) {

newX = spawn.x + dx;
newY = spawn.y + dy;
isOccupied = _.any(roomObjects, checkObstacleFn) ||
movement.isTileBusy(newX, newY) ||
(utils.checkTerrain(roomTerrain, newX, newY, C.TERRAIN_MASK_WALL) && !_.any(roomObjects, {type: 'road', x: newX, y: newY}));
isOccupied = utils.checkObstacleAtXY(newX, newY, spawn, roomIsInSafeMode, roomObjects, roomTerrain) ||
movement.isTileBusy(newX, newY);

if (!isOccupied) {
break;
}

// remember the first direction where we found a hostile creep
if(!hostileOccupied) {
// remember the first direction where we found a hostile creep (not under safemode)
if(!mySafeMode && !hostileOccupied) {
hostileOccupied = _.find(roomObjects, i => i.x == newX && i.y == newY && i.type == 'creep' && i.user != spawn.user);
}
}
Expand All @@ -57,8 +53,7 @@ module.exports = function(spawn, creep, scope) {

newX = spawn.x + dx;
newY = spawn.y + dy;
isOccupied = _.any(roomObjects, checkObstacleFn) ||
utils.checkTerrain(roomTerrain, newX, newY, C.TERRAIN_MASK_WALL) ||
isOccupied = utils.checkObstacleAtXY(newX, newY, spawn, roomIsInSafeMode, roomObjects, roomTerrain) ||
movement.isTileBusy(newX, newY);

if (!isOccupied) {
Expand Down
25 changes: 25 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,31 @@ exports.checkTerrain = function(terrain, x, y, mask) {
return (code & mask) > 0;
};

exports.checkObstacleAtXY = function(x, y, object, roomIsInSafeMode, roomObjects, roomTerrain, movingObjects) {
movingObjects = movingObjects || {};
var hasObstacle = false, hasRoad = false;
_.forEach(roomObjects, (i) => {
if (i.x != x || i.y != y) {
return;
}
if ((i.type == 'creep' || i.type == 'powerCreep') && !movingObjects[i._id] && (!roomIsInSafeMode || roomIsInSafeMode != object.user || roomIsInSafeMode == object.user && object.user == i.user) ||
i.type != 'creep' && i.type != 'powerCreep' && _.contains(C.OBSTACLE_OBJECT_TYPES, i.type) ||
i.type == 'rampart' && !i.isPublic && i.user != object.user ||
i.type == 'constructionSite' && i.user == object.user && _.contains(C.OBSTACLE_OBJECT_TYPES,
i.structureType)) {
hasObstacle = true;
return false;
}
if(i.type == 'road') {
hasRoad = true;
}
});
if(hasObstacle) {
return true;
}
return exports.checkTerrain(roomTerrain, x, y, C.TERRAIN_MASK_WALL) && !hasRoad;
};

exports.checkControllerAvailability = function(type, roomObjects, roomController, offset) {
var rcl = 0;

Expand Down