Skip to content

Instantly share code, notes, and snippets.

@robwalch
Created December 8, 2012 08:38
Show Gist options
  • Save robwalch/4239303 to your computer and use it in GitHub Desktop.
Save robwalch/4239303 to your computer and use it in GitHub Desktop.
HAWTBAWT
var r = 19;//Math.ceil(Math.sqrt(27 * 27 + 24 * 24)/2); // Tank Dimensions: 27, 24
var Robot = function(robot) {
this.data = {};
// MOVE_INCREMENT = 1, ANG_INCREMENT = 1;
this.time = 0;
this.arenaTopLeft = {x: r, y: r};
this.arenaTopRight = {x: robot.arenaWidth -r, y: r};
this.arenaBottomLeft = {x: r, y: robot.arenaHeight -r};
this.arenaBottomRight = {x: robot.arenaWidth -r, y: robot.arenaHeight -r};
this.arenaCenter = {x: robot.arenaWidth/2, y: robot.arenaHeight/2};
this.target1 = null;
this.target2 = null;
};
var p = Robot.prototype;
(function(){
var tankRadius = r;
var tankRadiusSq = r * r;
var bulletStrength = 20;
var bulletSpeed = 2;
var baseScanWaitTime = 50;
var baseGunCoolDownTime = 50;
var sub = function(v1, v2) {
return {x: v1.x - v2.x, y: v1.y - v2.y};
};
var add = function(v1, v2) {
return {x: v1.x + v2.x, y: v1.y + v2.y};
};
var neg = function(v) {
return {x: - v.x, y: - v.y};
};
var perp = function(v) {
return {x: -v.y, y: v.x};
};
var rperp = function(v) {
return {x: v.y, y: -v.x};
};
var mult = function(v, len) {
return {x: v.x * len, y: v.y * len};
};
var dot = function(v1, v2) {
return v1.x*v2.x + v1.y*v2.y;
};
var len = function(v1) {
return Math.sqrt(dot(v1, v1));
};
var dist = function(v1, v2) {
var v = sub(v1, v2);
return len(v, v);
};
var distSq = function(v1, v2) {
var v = sub(v1, v2);
return dot(v, v);
};
var tostr = function(v){
return !!v ? '('+v.x+', '+v.y+')' : '(undefined)';
};
var radian = function(a) {
return a * Math.PI / 180;
};
var forangle = function(a) {
a = radian(a);
return {x: Math.cos(a), y: Math.sin(a)};
};
var toangle = function(v) {
return Math.round(Math.atan2(v.y, v.x) * 180 / Math.PI + 90);
};
var normalizeAngle = function(a) {
return ((a % 360) + 360) % 360;
};
var shortest = function(a) {
a = normalizeAngle(a);
if (a > 180) return a - 360;
return a;
};
p.getBearing = function(robot, v) {
return shortest(toangle(sub(v, robot.position)) - robot.angle);
};
p.adjacentCorner = function(v) {
if (v.x > this.arenaCenter.x) {
if (v.y > this.arenaCenter.y) {
return this.arenaBottomLeft;
}
} else {
if (v.y > this.arenaCenter.y) {
return this.arenaTopLeft;
}
return this.arenaTopRight;
}
return this.arenaBottomRight;
};
p.gotoPos = function(robot, v) {
var data = this.data[robot.id];
var travelDistance;
if (data.maxDistanceBeforeIdle === 1) {
travelDistance = data.maxDistanceBeforeIdle;
} else {
travelDistance = Math.min(data.maxDistanceBeforeIdle, dist(v, robot.position));
}
//console.log(robot.id, 'heading', tostr(v), distance);
// determine optimal travel direction
var direction;
var bearing = this.getBearing(robot, v);
if (bearing < 90 || bearing > -90) {
direction = 1;
} else {
direction = -1;
bearing = shortest(bearing-180);
}
// if we prefer curved travel: direction = -direction;
var turnDirection;
if (bearing > 0) {
turnDirection = Math.min(data.maxTurnBeforeIdle, bearing);
} else {
turnDirection = Math.max(-data.maxTurnBeforeIdle, bearing);
}
data.turnDir = turnDirection;
data.dest = v;
robot.turn(turnDirection);
robot.move(travelDistance, direction);
};
p.atDestination = function(robot) {
var data = this.data[robot.id];
if (!data.dest) {
return true;
}
return distSq(data.dest, robot.position) < tankRadiusSq;
};
p.flank = function(robot, distance) {
var a = robot.angle - robot.cannonAngle;
console.log('flank pos', tostr(robot.position), 'a', a);
var vect = forangle(robot.angle - robot.cannonAngle);
console.log('flank v', tostr(vect));
var target1 = add(robot.position, mult(vect, distance));
return target1;
};
// TODO: separate movement from scanning and aiming
// TODO: scanVectors []
p.rayray = function(pos, a, b)
{
// offset the line to be relative to the circle
a = sub(a, pos);
b = sub(b, pos);
var qa = dot(a, a) - 2*dot(a, b) + dot(b, b);
var qb = -2*dot(a, a) + 2*dot(a, b);
var qc = dot(a, a) - tankRadius*tankRadius;
var det = qb*qb - 4*qa*qc;
if(det >= 0) {
var t = (-qb - Math.sqrt(det))/(2*qa);
if(0 <= t && t <= 1){
return true;
}
}
return false;
};
p.aimAt = function(robot, v) {
var bearing = shortest(this.getBearing(robot, v) - robot.cannonRelativeAngle + 90);
// TODO: make sure the other tank is not inside the attack vector
var vect = sub(v, robot.position);
var sibData = this.getSiblingData(robot);
var targetBlocked = false;
if (sibData && !sibData.dead) {
if (this.rayray(sibData.robot.position, robot.position, v)) {
//console.log(robot.id, 'GONNA HIT THAT ROBOT', sibData.robot.id);
targetBlocked = true;
}
}
if (bearing === 0 && !targetBlocked) {
return true;
}
// determine if we need to turn before firing
var distance = dist(v, robot.position);
var margin = 180 - toangle({x: 17, y: distance});
//only turn in increments of 1
var direction = (bearing > 0) ? 1 : -1;
robot.rotateCannon(direction);
if (targetBlocked || Math.abs(bearing-direction) > Math.abs(margin)) {
return false;
}
//target locked
return true;
};
p.aimAtTarget = function(robot, target) {
return this.aimAt(robot, target.position);
};
p.fireAt = function(robot, target) {
var data = this.data[robot.id];
// target.shotsToFire = Math.ceil(other.life / 20); // bullet strength = 20
if (target.shotsToFire > 0) {
if (this.aimAtTarget(robot, target)) {
//console.log(robot.id, 'firing at '+ tostr(target.position), 'enemy life: '+ target.life, 'shots to fire: '+ target.shotsToFire, 'time:'+ this.time);
robot.fire();
target.shotsToFire--;
}
}
};
p.initData = function(robot) {
var data = this.data[robot.id];
if (!data) {
data = {};
this.data[robot.id] = data;
data.isClone = !!robot.parentId;
if (data.isClone) {
this.getSiblingData(robot).cloneId = robot.id;
}
data.lastAngle = robot.angle;
data.lastTurn = 0;
data.maxTurnBeforeIdle = 20;
data.maxDistanceBeforeIdle = 20;
data.robot = robot;
//console.log(robot.id, 'initialized. pos', tostr(robot.position), robot.angle, data.isClone ? 'clone' : '');
}
return data;
};
p.getSiblingData = function(robot) {
if (robot.parentId) {
return this.data[robot.parentId];
}
return this.data[this.data[robot.id].cloneId];
};
p.onIdle = function(ev) {
var robot = ev.robot;
var data = this.initData(robot);
data.robot = robot;
if (!robot.parentId) {
this.time++;
}
//should fire?
var enemy = this.data.enemy || this.data.enemyClone;
if (enemy) {
if (!robot.parentId) {
enemy.timeSinceAquisition++;
enemy.timeUntilCollision--;
}
if (enemy.life <= 0) {
//dead clone
this.data.enemyClone = null;
} else {
if (enemy.shotsToFire <= 0) {
if (enemy.timeSinceAquisition > this.baseScanWaitTime) {
if (data.isClone) {
// TODO: list targets track last scan time and cycle through list
this.aimAt(robot, this.target2 || {x: robot.arenaWidth * Math.random(), y: robot.arenaHeight * Math.random()});
} else {
this.aimAt(robot, this.target1 || {x: robot.arenaWidth * Math.random(), y: robot.arenaHeight * Math.random()});
}
}
} else {
if (robot.gunCoolDownTime <= 0) {
this.fireAt(robot, enemy);
return;
} else {
// old enough to have evaded
if (enemy.timeSinceAquisition > tankRadius) { }
// we should have gotten a scan update if they were still in sight
if (enemy.timeSinceAquisition > this.baseScanWaitTime) { }
this.aimAtTarget(robot, enemy);
// evade in-case enemy guesses out pos after being hit
if (enemy.timeUntilCollision <= 0) {
this.gotoPos(robot, this.flank(enemy, tankRadius*5));
}
}
}
}
}
if (robot.availableClones) {
robot.clone();
return;
}
// should position?
if (data.dest && !this.atDestination(robot)) {
// make sure parent and clone detinations are not exactly the same
var sibData = this.getSiblingData(robot);
if (sibData && !sibData.dead) {
var sibDest = sibData.dest;
if (sibDest && distSq(sibDest, data.dest) < tankRadiusSq) {
var vect, distance;
if (distSq(robot, data.dest) > distSq(sibData.robot, sibDest)) {
//let sibling go first
vect = sub(data.dest, robot.position);
distance = dist(vect);
data.dest = add(robot.position, mult(vect, (distance - tankRadius*2)/ distance));
} else if (sibData.robot && sibData.robot.position) {
//tell them off
vect = sub(sibData.dest, sibData.robot.position);
distance = dist(vect);
sibData.dest = add(sibData.robot.position, mult(vect, (distance - tankRadius*2)/ distance));
}
}
}
this.gotoPos(robot, data.dest);
if (!enemy) {
var direction = data.turnDir || data.isClone ? 1 : -1;
robot.rotateCannon(direction);
} else {
if (data.isClone) {
// TODO: list targets track last scan time and cycle through list
this.aimAt(robot, this.target2 || {x: robot.arenaWidth * Math.random(), y: robot.arenaHeight * Math.random()});
} else {
this.aimAt(robot, this.target1 || {x: robot.arenaWidth * Math.random(), y: robot.arenaHeight * Math.random()});
}
}
return;
}
//should seek?
if (this.atDestination(robot)) {
this.gotoPos(robot, this.adjacentCorner(robot.position));
}
};
p.friendOrFoe = function(robot, other) {
var data = this.initData(robot);
if (other.parentId == robot.id) {
//my clone
//console.log(robot.id, ': <o> my clone');
return 1;
}
if (other.id == robot.parentId) {
//mama
//console.log(robot.id, ': <o> my parent');
return 1;
}
// an enemy
return this.aquireTarget(robot, other);
};
p.aquireTarget = function(robot, other) {
var data = this.initData(robot);
if (other.life === 0) {
return 0;
}
var pos = robot.position;
// add data about target
other.shotsToFire = Math.ceil(other.life / bulletStrength);
other.timeUntilCollision = dist(pos, other.position) / bulletSpeed;
other.timeSinceAquisition = 0;//time since aquisition
other.predictions = []; // TODO: positions given angle + forward/backward movement & walls
//robot Dimension(27, 24)
// TODO: are we in it's sights? see: robotInSight
// other.cannonAngle
// other.angle
if ((this.data.enemy && other.id === this.data.enemy.id)
||(this.data.enemyClone && other.id === this.data.enemyClone.id)) {
// Updating existing target
// TODO: time since last update
// TODO: Movement vector
} else {
// New target
// TODO: start tracking known locations over time
}
// Save target object
if (other.parentId) {
//console.log(robot.id, ': <o> my enemy clone. life', other.life, 'angle', other.angle, 'time:'+ this.time);
this.data.enemyClone = other;
return -1;
}
//console.log(robot.id, ': <o> my enemy. life', other.life, 'angle', other.angle, 'time:'+ this.time);
this.data.enemy = other;
return -1;
};
p.onScannedRobot = function(ev) {
var robot = ev.robot;
var other = ev.scannedRobot;
var data = this.initData(robot);
var avoid;
//console.log(robot.id, '> Scanned:', other.id, 'vect: '+ tostr(sub(other.position, robot.position)));
if (this.friendOrFoe(robot, other) < 0) {
//console.log('stopping and firing. queue:', robot.queue.length, robot.queue[0].action +' x '+ robot.queue[0].count ? robot.queue[0].count : robot.queue[0].eventName);
robot.stop();
if (!this.atDestination(robot)) {
return;
}
var enemy = this.data.enemy || this.data.enemyClone;
data.dest = enemy.position;
this.fireAt(robot, enemy);
var vect = forangle(enemy.angle);
this.target1 = sub(enemy.position, mult(vect, 50));
this.target2 = sub(enemy.position, mult(vect, -50));
} else {
//scanned friend. use 50 cycles to get a better vantage
var bearing = shortest(this.getBearing(robot, other.position) - robot.cannonRelativeAngle + 90);
var margin;
if (bearing === 0) {
margin = 17;
} else {
var distance = dist(other.position, robot.position);
margin = 180 - toangle({x: 17, y: distance});
}
//only turn in increments of 1
var direction = (margin > 0) ? 1 : -1;
robot.rotateCannon(direction);
//console.log(robot.id +' aiming', 'bearing: '+bearing, 'margin: '+margin, 'direction: '+direction, 'distance: '+distance, 'canon', robot.cannonRelativeAngle);
}
};
p.onRobotCollision = function(ev) {
var robot = ev.robot;
var other = ev.collidedRobot;
var data = this.initData(robot);
//console.log(robot.id, '> Collision:', other.id, ev.myFault, 'queue:', robot.queue.length, robot.queue[0].action +' x '+ robot.queue[0].count ? robot.queue[0].count : robot.queue[0].eventName);
if (this.friendOrFoe(robot, other) < 0) {
robot.stop();
var enemy = this.data.enemy || this.data.enemyClone;
this.fireAt(robot, enemy);
if (!enemy.parentId) {
var vect = sub(enemy.position, robot.position);
this.target1 = sub(enemy.position, mult(vect, 50));
this.target2 = sub(enemy.position, mult(vect, -50));
}
} else {
var avoid = add(robot.position, sub(robot.position, other.position));
data.dest = avoid;
}
};
p.onHitByBullet = function(ev) {
var robot = ev.robot;
var data = this.initData(robot);
if (robot.life === 0) {
if (robot.parentId) {
data.dest = null;
data.dead = true;
return;
}
}
var angle = shortest(robot.angle - ev.bearing);
var vect = forangle(angle);
// TODO: check bearing and make sure it's not clone/master bullet
var sibData = this.getSiblingData(robot);
if (sibData && !sibData.dead) {
var angle2 = toangle(sub(sibData.robot.position, robot.position));
console.log('bullet @ '+ angle, 'sib @ '+ angle2, tostr(sibData.robot.position), tostr(vect));
//console.log(robot.id, '> hit', ev.bearing, tostr(v), 'queue:', robot.queue.length, robot.queue[0].action +' x '+ robot.queue[0].count ? robot.queue[0].count : robot.queue[0].eventName);
if (Math.abs(angle - angle2) > 30) {
//likey an enemy
this.target1 = sub(robot.position, mult(vect, 50));
this.target2 = sub(robot.position, mult(vect, robot.arenaWidth));
}
}
//evade if not firing on enemy
var enemy = this.data.enemy || this.data.enemyClone;
if (!enemy || enemy.timeSinceAquisition > this.baseScanWaitTime) {
var p1 = mult(perp(vect), 100);
var p2 = mult(rperp(vect), 100);
if (distSq(p1, this.arenaCenter) < distSq(p2, this.arenaCenter)) {
this.gotoPos(robot, add(robot.position, p1));
} else {
this.gotoPos(robot, add(robot.position, p2));
}
}
};
p.onWallCollision = function(ev) {
var robot = ev.robot;
var data = this.initData(robot);
//console.log(robot.id, '> wall collision', tostr(robot.position));
if (data.dest) {
// just turn
var bearing = this.getBearing(robot, data.dest);
robot.turn(bearing);
data.turnDir = bearing;
}
data.dest = this.arenaCenter;
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment