Created
December 8, 2012 08:38
-
-
Save robwalch/4239303 to your computer and use it in GitHub Desktop.
HAWTBAWT
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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