12
\$\begingroup\$

This is a game of capture the flag, heavily inspired and based off of Red vs. Blue - Pixel Team Battlebots. That was an awesome question (thank you very much Calvin'sHobbies; I hope you don't mind that I shamelessly stole a lot of code from you) -- here's another team based king-of-the-hill. Hopefully, capture the flag will require more team cooperation as well as more strategy.

To mix it up, you are considered on the red team if the last digit of your id is between 0 and 4 inclusive. This should prevent the exact same teams from battling again, if the same people decide to answer. The board is 350px by 350px. The blue team starts on the upper half of the board and the red team starts on the lower half.

The way you play capture the flag is as follows: the object of the game is to take the opposing team's flag and bring it back to your own side. If you are on their side, you can be tagged and sent to jail. If you are in jail, then you can't move. If you are on your side, your job is to tag opposing team members to send them to jail. The only way to get out of jail is for someone on your team who is free to tag everyone in jail. (Note that jail is located on the opposing team's side).

Specifically:

  • There is a constant - FIELD_PADDING - set to 20. This is the padding for the field. If it were zero, then the flags and jail would be exactly on the corners of the canvas. Since it is not, the flag and jail are 20 pixels away from the corners.
  • The blue flag (remember: blue team is on the upper half) is located at (WIDTH - FIELD_PADDING, FIELD_PADDING) = (330, 20) i.e. top-right corner.
  • The red flag is at (FIELD_PADDING, HEIGHT - FIELD_PADDING) = (20, 330)
  • The blue jail (where red members are kept) is at (20, 20) i.e. blue side, top left.
  • The red jail, where blue members are kept, is at (330, 330)

Every team member starts randomly at a position 45 < x < 305 and 45 < y < 175 for blue and 175 < y < 305 for red. No team member can go within DEFENSE_RADIUS = 25 pixels of their own flag or their own jail (unless, of course, your own flag was taken by an opposing bot, in which case you need to tag that bot). This is to prevent puppy-guarding like bots. If you go within that range, you are "pushed" back. Similarly, no team member can go out of bounds (less than zero or more than 350) -- if you do, you are pushed back to the nearest legal place you can be.

Every time you move, you use up strength. Your strength starts out at 20 and is replenished by 2 every turn. The amount of strength you use is equal to the distance you travel. If your strength would become negative by moving to a certain place, you are prevented from making that move. It's probably a good idea to just go at speed 2 for normal chasing. You should only use higher speeds if you are close to winning and need the extra speed (in my opinion).

Spec:

The spec is quite similar to the Pixel Team Battlebots question. You should write a code block (remember, no global variables) in javascript. It should return an object with an x-value and y-value representing your change in x and change in y values. The following answer:

return {
  x: 0,
  y: -2
};

always moves up, until it hits a wall. You may not edit 8 hours after posting (except for LegionMammal98 who thought that the controller wasn't loading his/her code and didn't test). You have access to the following variables in your code:

  • this -- yourself, as a player (see below for what players are)
  • move -- the round number, starting at 0
  • tJailed -- an array of all players on your team that are jailed
  • eJailed -- an array of all players on the opposing team that are jailed
  • team -- an array of all players on your team, NOT just the ones near you
  • enemies -- an array of all players on the other team, NOT just the ones near you
  • tFlag -- your flag (you're trying to protect it)
  • eFlag -- the other flag (you're trying to steal it)
  • messages -- explained below
  • A list of constants: WIDTH = 350, HEIGHT = 350, FIELD_PADDING = 20, DEFENSE_RADIUS = 25.

Every "player" is an object with the following properties:

  • x and y
  • strength
  • id
  • isJailed -- true if the player is in jail

Every flag has the following properties:

  • x and y
  • pickedUpBy -- the player who currently has the flag, or null if no player has the flag.

Now, messages is an object that is shared among your teammates. I don't care what you do with it. The same object is shared and passed to every one of your team members. This is the only way you can communicate. You can attach properties to it, share objects, etc. It can be as big as you want -- no size limit.

Every turn the following happens:

  • The list of players (both red and blue) is randomly shuffled for turn order.
  • Every player makes a move.
  • If any red team members touch (within 10 pixels of) any blue team members on red's side, send the blue team members to jail, and vice versa. A jailed player drops his/her flag and has strength drop to zero. Note that the step function (code you provide) is still called -- so you can get/set messages, but you can't move while in jail.
  • If any player is touching (within 10 pixels of) the other flag, then the other flag is marked as "picked up by" that player. When the player moves, the flag moves -- until the player is tagged and goes to jail, that is.
  • If any player is touching the other side's jail, free everyone in that jail. When a player is freed from jail, he/she is teleported to a random location on his/her side.

Hints:

  • At least in regular capture the flag, attacks work much better when many players go at once, because it tends to confuse defenders as to which player they should chase.
  • Similarly, defenders might want to coordinate who they are chasing so that attacks don't go through

Stack snippet:

window.onload=function(){(function(){function p(a,b,c,e){return Math.sqrt((a-c)*(a-c)+(b-e)*(b-e))}function l(a,b){this.x=this.y=0;this.id=a.id;this.title=a.title+" ["+this.id+"]";this.link=a.link||"javascript:;";this.team=b;this.isJailed=!1;this.flag=null;this.moveFn=new Function("move","tJailed","eJailed","team","enemies","tFlag","eFlag","messages","WIDTH","HEIGHT","FIELD_PADDING","DEFENSE_RADIUS",a.code);this.init()}function x(a,b){return Math.floor(Math.random()*(b-a))+a}function q(a,b){this.startX=this.x=a;this.startY=
this.y=b;this.following=null}function t(a,b){return a===e&&b||a===h&&!b?{x:20,y:20}:{x:g.width-20,y:g.height-20}}function y(){var a,b=$("#redTeam"),c=$("#blueTeam");for(a=0;a<e.length;++a)e[a].addToDiv(b);for(a=0;a<h.length;++a)h[a].addToDiv(c)}function z(){d.clearRect(0,0,g.width,g.height);d.beginPath();d.moveTo(0,g.height/2);d.lineTo(g.width,g.height/2);d.stroke();var a=e.concat(h),b,c;for(b=a.length-1;0<b;b--){c=Math.floor(Math.random()*(b+1));var f=a[b];a[b]=a[c];a[c]=f}for(b=0;b<a.length;++b)a[b].step(u);
for(b=0;b<e.length;++b)for(c=0;c<h.length;++c)10>p(e[b].x,e[b].y,h[c].x,h[c].y)&&(e[b].y<g.height/2&&e[b].goToJail(),h[c].y>g.height/2&&h[c].goToJail());for(b=0;b<a.length;++b)c=a[b].team===e!==!0?m:n,!c.following&&10>p(a[b].x,a[b].y,c.x,c.y)&&(c.following=a[b]);for(b=0;b<a.length;++b)if(c=t(a[b].team,!0),!a[b].isJailed&&10>p(a[b].x,a[b].y,c.x,c.y))for(c=a[b].team,f=0;f<c.length;++f)c[f].isJailed&&(c[f].isJailed=!1,c[f].init());m.follow();n.follow();b=m.y<g.height/2;c=n.y>g.height/2;b&&c&&alert("EXACT TIE!!!! This is very unlikely to happen.");
b&&!c&&(alert("Blue wins!"),$("#playpause").click().hide());c&&!b&&(alert("Red wins!"),$("#playpause").click().hide());for(b=0;b<a.length;++b)a[b].draw(d);m.draw("red");n.draw("blue");u++}$.ajaxSetup({cache:!1});var e=[],h=[],g=$("canvas")[0],d=g.getContext("2d"),v,u=0,m={},n={},r=!0,A={},B={},w;l.prototype.init=function(){this.x=x(45,g.width-45);this.y=x(45,g.height/2);this.team===e&&(this.y+=g.height/2);this.strength=20};l.prototype.makeShallowCopy=function(){return{x:this.x,y:this.y,strength:this.strength,
id:this.id,isJailed:this.isJailed}};l.prototype.goToJail=function(){this.isJailed=!0;var a=this.team===e!==!0?m:n;(this.team===e!==!0?m:n).following===this&&(a.following=null);a=t(this.team,!0);this.x=a.x;this.y=a.y;this.strength=0};l.prototype.step=function(a){function b(a,b,c){var e,d,f;for(e=0;e<a.length;++e)d=a[e],d!==C&&(f=d.makeShallowCopy(),d.isJailed?b.push(f):c.push(f))}var c=[],f=[],d=[],k=[],l=this.team===e?h:e,C=this,q=this.team===e?m:n,r=this.team===e?n:m;b(this.team,c,d);b(l,f,k);f=
this.moveFn.call(this.makeShallowCopy(),a,c,f,d,k,q.copy(),r.copy(),this.team===e?A:B,g.width,g.height,20,25);"object"===typeof f&&"number"===typeof f.x&&"number"===typeof f.y&&(d=p(0,0,f.x,f.y),a=t(this.team,!1),c=this.team===e!==!1?m:n,d<=this.strength&&(this.strength-=d,this.x+=f.x,this.y+=f.y,0>this.x&&(this.x=0),0>this.y&&(this.y=0),this.x>g.width&&(this.x=g.width),this.y>g.height&&(this.y=g.height),f=p(this.x,this.y,c.x,c.y),d=p(this.x,this.y,a.x,a.y),25>f&&null===c.following&&(this.x=25*(this.x-
c.x)/f*1.3+c.x,this.y=25*(this.y-c.y)/f*1.3+c.y),25>d&&(this.x=25*(this.x-a.x)/d*1.3+a.x,this.y=25*(this.y-a.y)/d*1.3+a.y)),this.isJailed||(this.strength+=2),20<this.strength&&(this.strength=20))};l.prototype.addToDiv=function(a){var b=$("<option>").text(this.title).val(this.id);a.find(".playersContainer").append(b)};l.prototype.draw=function(a){a.fillStyle=this.team===e?"red":"blue";a.beginPath();a.arc(this.x,this.y,5,0,2*Math.PI,!0);a.fill();!this.isJailed&&$("#labels").is(":checked")&&a.fillText(this.title,
this.x+5,this.y+10)};q.prototype.draw=function(a){d.strokeStyle=a;d.beginPath();d.arc(this.x,this.y,5,0,2*Math.PI,!0);d.stroke();d.fillStyle=a;d.strokeRect(this.x-2,this.y-2,4,2);d.beginPath();d.moveTo(this.x-2,this.y);d.lineTo(this.x-2,this.y+3);d.stroke()};q.prototype.copy=function(){return{x:this.x,y:this.y,pickedUpBy:this.following&&this.following.makeShallowCopy()}};q.prototype.follow=function(){null!==this.following&&(this.x=this.following.x,this.y=this.following.y)};$("#newgame").click(function(){function a(a,
b){w?b(w):$.get("https://api.stackexchange.com/2.2/questions/"+(49028).toString()+"/answers",{page:a.toString(),pagesize:100,order:"asc",sort:"creation",site:"codegolf",filter:"!JDuPcYJfXobC6I9Y-*EgYWAe3jP_HxmEee"},b,"json")}function b(g){w=g;g.items.forEach(function(a){function b(a){return $("<textarea>").html(a).text()}var d=4>=a.owner.user_id%10?e:h;a.owner.display_name=b(a.owner.display_name);if(!(a.hasOwnProperty("last_edit_date")&&28800<a.last_edit_date-a.creation_date&&33208!==a.owner.user_id||
-1<p.indexOf(a.owner.user_id))){p.push(a.owner.user_id);var g=c.exec(a.body);if(!(null===g||1>=g.length)){var f={};f.id=a.owner.user_id;f.title=a.owner.display_name;f.code=b(g[1]);f.link=a.link;d.push(new l(f,d))}}});g.has_more?a(++d,b):(console.log("Red team",e),console.log("Blue team",h),y(),clearInterval(v),r=!0,$("#playpause").show().click())}var c=/<pre><code>((?:\n|.)*?)\n<\/code><\/pre>/,d=1,p=[];e=[];h=[];u=0;m=new q(20,g.height-20);n=new q(g.width-20,20);$(".teamColumn select").empty();var k=
$("#testbotCode").val();0<k.length&&(console.log("Using test entry"),k={title:"TEST ENTRY",link:"javascript:;",code:k},$("#testbotIsRed").is(":checked")&&(k.id=-1,e.push(new l(k,e)),k.id=-3,e.push(new l(k,e))),$("#testbotIsBlue").is(":checked")&&(k.id=-2,h.push(new l(k,h)),k.id=-4,h.push(new l(k,h))));a(1,b)});$("#playpause").hide().click(function(){r?(v=setInterval(z,25),$(this).text("Pause")):(clearInterval(v),$(this).text("Play"));r=!r})})();}
#main{padding:10px;text-align:center}#testbot{padding:10px;clear:both}.teamColumn{width:25%;padding:0 10px;border:3px solid;border-color:#000;text-align:center;height:500px;overflow:scroll;white-space:nowrap}.playersContainer p{padding:0;margin:0}#redTeam{float:left;border-color:red;color:red;background-color:#fee}#blueTeam{float:right;border-color:#00f;color:#00f;background-color:#fee}#arena{display:inline-block;width:40%;text-align:center}canvas{border:1px solid #000}select{width:100%}
<script src=https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js></script><div id=main><div class=teamColumn id=redTeam><h1>Red Team</h1><select size=20 class=playersContainer></select></div><div id=arena><h1>Battlefield</h1><canvas width=350 height=350></canvas></div><div class=teamColumn id=blueTeam><h1>Blue Team</h1><select size=20 class=playersContainer></select></div><div id=loadingInfo><button id=newgame>New Game</button> <button id=playpause>Play</button><br><input type=checkbox id="labels"> Show labels</div></div><div id=testbot><textarea id=testbotCode placeholder="testbot code"></textarea><br><input type=checkbox id="testbotIsRed">Red Team<br><input type=checkbox id="testbotIsBlue">Blue Team<br></div>

Controller: http://jsfiddle.net/prankol57/4L7fdmkk/

Full screen controller: http://jsfiddle.net/prankol57/4L7fdmkk/embedded/result/

Let me know if there are any bugs in the controller.

Note: If you go to the controller and think that it isn't loading anything, press "New Game." It only loads everything after you press "New Game" so that it can load all the bots and possible test bots at once.

Good luck.


If anyone wants to see an example game, I made an example bot that you can copy and paste into the "testbot" textarea (the testbot creates two duplicates on each team; check both red team and blue team):

var r2 = Math.sqrt(2);
if (this.id === -1) {
  // red team 1
  // go after flag regardless of what is going on
  if (eFlag.pickedUpBy !== null && eFlag.pickedUpBy.id === this.id) {
    return {
      x: 0,
      y: 2
    };
  }
  return {
    x: this.x < eFlag.x ? r2 : -r2,
    y: this.y < eFlag.y ? r2 : -r2
  };
}
if (this.id === -2) {
  // blue team 1
  // a) go after opposing team members on your side b) get the other flag if no enemies on your side
  var closestEnemy = null;
  for (var i = 0; i < enemies.length; ++i) {
    if (enemies[i].y < HEIGHT/2 && (closestEnemy === null || enemies[i].y < closestEnemy.y)) {
      closestEnemy = enemies[i];
    }
  }
  if (closestEnemy !== null) {
    return {
      x: this.x < closestEnemy.x ? r2 : -r2,
      y: this.y < closestEnemy.y ? r2 : -r2
    };
  }
  if (eFlag.pickedUpBy !== null && eFlag.pickedUpBy.id === this.id) {
    return {
      x: 0,
      y: -2
    };
  }
  return {
    x: this.x < eFlag.x ? r2 : -r2,
    y: this.y < eFlag.y ? r2 : -r2
  };
}
if (this.id === -3) {
  // red team 2
  // a) defend the flag b) if at least half of enemies in jail and no enemies on this side, free jailed reds and quickly return
  var closestEnemy = null;
  for (var i = 0; i < enemies.length; ++i) {
    if (enemies[i].y > HEIGHT/2 && (closestEnemy === null || enemies[i].y > closestEnemy.y)) {
      closestEnemy = enemies[i];
    }
  }
  if (closestEnemy !== null) {
    return {
      x: this.x < closestEnemy.x ? r2 : -r2,
      y: this.y < closestEnemy.y ? r2 : -r2
    };
  }
  if (enemies.length / eJailed.length <= 1 && tJailed.length > 0) {
    return {
      x: this.x < FIELD_PADDING ? r2 : -r2,
      y: this.y < FIELD_PADDING ? r2 : -r2
    };
  }
  if (this.y < 350/2) return {x: 0, y: 2};
  return {
    x: this.x < tFlag.x ? r2 : -r2, 
    y: this.y < tFlag.y ? r2 : -r2
  };
}
if (this.id === -4) {
  // blue team 2
  // a) try freeing jail if there are jailed team members b) capture the flag
  if (tJailed.length > 0) {
    return {
      x: this.x < WIDTH - FIELD_PADDING ? r2 : -r2,
      y: this.y < HEIGHT - FIELD_PADDING ? r2 : -r2
    };
  }
  if (eFlag.pickedUpBy !== null && eFlag.pickedUpBy.id === this.id) {
    return {
      x: 0,
      y: -2
    };
  }
  return {
    x: this.x < eFlag.x ? r2 : -r2,
    y: this.y < eFlag.y ? r2 : -r2
  };
}
\$\endgroup\$
  • 8
    \$\begingroup\$ You may want to post this in meta as a sandbox post first (or even concurrently) like I did with RvB. It's a complicated type of contest, and having a place you and others can debug stuff is very helpful. (Btw, I don't mind you using my code, though I can't say it was documented or even organized terribly well :P) \$\endgroup\$ – Calvin's Hobbies Apr 20 '15 at 3:19
  • 1
    \$\begingroup\$ It would be halpful if you changed the controller link to jsfiddle.net/prankol57/4L7fdmkk/embedded/result for fullscreen. \$\endgroup\$ – LegionMammal978 Apr 20 '15 at 10:08
  • 2
    \$\begingroup\$ Isn't the controller one of the most important parts...? \$\endgroup\$ – Alex A. Apr 20 '15 at 14:21
  • 1
    \$\begingroup\$ @AlexA Yeah, but how would posting it in the sandbox help fix bugs in the controller (not loading answers, running the answers)? People would have to start posting actual answers that work, which isn't, in my opinion, what meta is for, which means I should probably just post it here. Bugs will inevitably come up even in regular KOTH controllers. \$\endgroup\$ – soktinpk Apr 20 '15 at 20:22
  • 1
    \$\begingroup\$ My bot isn't appearing on the controller. \$\endgroup\$ – LegionMammal978 Apr 21 '15 at 10:13
4
\$\begingroup\$

Red - Lazy Jail Hog | Lazy Flagger

Moves towards the closer of these two: blue's jail, or blue's flag.

  • If going for the jail, will move into the jail and stop. (Since blue can't touch its own jail, it will be invincible and automatically free all allies)
  • If going for the flag, it will blindly move for the flag and return.

Finally, its brain is entirely stored in messages[29354] and initialized on the first move only. Thus, if allies find a better use for this bot, they can replace its brain for their higher purpose.

if (move === 0) {
    //On the first turn, set messages[this.id] to the function I will call to move me
    messages[this.id] = function(move, tJailed, eJailed, team, enemies, tFlag, eFlag, messages) {
        //Arbitrary function to move to a point at some speed, which may be in the point
        //  If we are at the point, undefined is returned
        var moveTo = function(p, max) {
            if (!p) {
                return {x:0, y:0};
            }
            max = Math.min(this.strength, max || p.max || 2);
            var dx = p.x - this.x;
            var dy = p.y - this.y;
            var dist = Math.abs(dx)+Math.abs(dy);
            if (dist === 0) {
                return undefined; 
            } else if (dist < max) {
                return {x: dx, y: dy};
            }
            var ux = Math.floor(max * dx / dist);
            var uy = Math.floor(max * dy / dist);
            while (Math.abs(ux) + Math.abs(uy) < max) {
                if (ux + this.x !== p.x) {
                    ux += ux > 0 ? 1 : -1;
                } else if (uy + this.y !== p.y) {
                    uy += uy > 0 ? 1 : -1;
                } else {
                    break;
                }
            }
            return {x: ux, y:uy};
        }.bind(this);

        //Set the way points
        var points = [];
        if (this.x > WIDTH/2) {
            points.push({x: WIDTH-FIELD_PADDING, y:HEIGHT/2+5});
            points.push({x: WIDTH-FIELD_PADDING, y:FIELD_PADDING, max: 5});
            points.push({x: WIDTH-FIELD_PADDING, y:HEIGHT/2+25, max: 5});
        } else {
            points.push({x: FIELD_PADDING, y:HEIGHT/2+5});
            points.push({x: FIELD_PADDING, y:FIELD_PADDING, max: 5});
            points.push(undefined); //Special case to do nothing / hog the jail
        }

        //Move through the points
        var state = messages[this.id].state || 0;
        var ret;
        while (!ret) {
            //Special case: if we were doing nothing, make sure we're where we think we were
            if (!points[state]) {
                ret = moveTo(points[state-1]);
                if (ret) {
                    state = 0;
                }
            }

            //Move to the next point
            ret = moveTo(points[state]);
            if (!ret) {
                state = (state + 1) % points.length;
            }
        }
        messages[this.id].state = state;
        return ret;
    };
}
//Move me based on that function, which may be changed by my allies
return messages[this.id].call(this, move, tJailed, eJailed, team, enemies, tFlag, eFlag, messages);
\$\endgroup\$
  • \$\begingroup\$ The controller uses euclidean distance for movements. \$\endgroup\$ – TheNumberOne Apr 22 '15 at 22:12
4
\$\begingroup\$

Red - The Guard

This bot will guard the flag pretty good. Don't get in its way...

if (!messages[this.id]) {
    //On the first turn, set messages[this.id] to the function I will call to move me. You can replace this function on subsequent turns
    //to control it. Additionally, you can use it as a library to find one of the best places to go to defend.
    messages[this.id] = function(move, tJailed, eJailed, team, enemies, tFlag, eFlag, messages, WIDTH, HEIGHT, FIELD_PADDING, DEFENSE_RADIUS) {
        var distance = function(p1, p2) {
            var dx = p1.x - p2.x;
            var dy = p1.y - p2.y;
            return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
        }

        var moveTo = function(p) {
            if (!p) {
                return {x:0, y:0};
            }
            var dx = p.x - this.x;
            var dy = p.y - this.y;
            var max = this.strength;
            var dist = distance(p, this);
            if (dist < max) {
                return {x: dx, y: dy};
            }
            dx = dx * max / dist;
            dy = dy * max / dist;
            while (Math.sqrt(Math.abs(dx)+Math.abs(dy)) > max) {
                if (dx > dy) {
                    dx = dx - 0.001;
                } else {
                    dy = dy - 0.001;
                }
            }
            return {x: dx, y:dy};
        }.bind(this);

        if (tFlag.pickedUp) {
            if (tFlag.y - HEIGHT / 2 > distance(this, {x: tFlag.x, y: HEIGHT / 2})) {
                return moveTo(tFlag);
            } else {
                return moveTo({y: Math.min(this.y, tFlag.y), x: tFlag.x });
            }
        }

        if (eFlag.pickedUp == this.id) {
            return moveTo({x: x, y: HEIGHT / 2});            
        }

        var targetPoints = [];
        var crossedBorder = false;

        var weightedMiddlePoint = function(enemy) {
            var x1 = (enemy.x + tFlag.x) / 2;
            var y1 = (enemy.y + tFlag.y) / 2;
            var w = 1/Math.pow(distance(enemy, tFlag),2);
            return {x:x1,y:y1,w:w};
        }

        for (var i = 0; i < enemies.length; i++) {
            var enemy = enemies[i];
            if (enemy.isJailed){
                continue;
            }
            if (enemy.y > HEIGHT / 2) {
                crossedBorder = true;
            }
        }

        for (var i = 0; i < enemies.length; i++) {
            enemy = enemies[i];
            if (enemy.isJailed){
                continue;
            }
            if (crossedBorder) {
                if (enemy.y > HEIGHT / 2) {
                    targetPoints.push(weightedMiddlePoint(enemy));
                }
            } else {
                targetPoints.push(weightedMiddlePoint(enemy));
            }
        }

        if (targetPoints.length == 0) {
            return moveTo(eFlag);
        }

        var sumX = 0;
        var sumY = 0;
        var sumW = 0;

        for (var i = 0; i < targetPoints.length; i++) {
            point = targetPoints[i];
            sumX += point.x * point.w;
            sumY += point.y * point.w;
            sumW += point.w;
        }

        var targetPoint = {x: sumX / sumW, y: sumY / sumW};

        return moveTo(targetPoint);

    };
}

return messages[this.id].call(this, move, tJailed, eJailed, team, enemies, tFlag, eFlag, messages, WIDTH, HEIGHT, FIELD_PADDING, DEFENSE_RADIUS);
\$\endgroup\$
4
\$\begingroup\$

Blue - LegionMammal978

function repeat(el, n) // Helper function
{
    var rtn = [];
    for (var i = 0; i < n; i++)
        rtn.push(el);
    return rtn;
}
function sign(n) { return n ? n < 0 ? -1 : 1 : 0; } // Another helper function
if (!messages[this.id])
    messages[this.id] = { "dir": 1 };
if (this.isJailed) // Oh noes, I'm in jail!
{
    console.log(this.id, messages);
    if (!messages[this.id].jailTicks)
        messages[this.id].jailTicks = 0;
    messages[this.id].jailTicks++;
    // Call for help!
    messages.callsForHelp = repeat(["Help!", this.id, this.x, this.y], messages[this.id].jailTicks);
    return { "x": 0, "y": 0 };
}
if (messages[this.id].jailTicks)
    if (!(delete messages[this.id].jailTicks && delete messages.callsForHelp)) // Cleanliness
        messages[this.id].jailTicks = messages.callsForHelp = undefined;       // ...
var bounds = Math.floor(HEIGHT / 2); // Be safe with fractions
if (this.y > bounds - 5) // Get back to shelter!
    return { "x": 0, "y": this.y - this.strength <= bounds - 5 ? bounds - 5 - this.y : -4 };
var target = { "none": true, "x": WIDTH << 1, "y": HEIGHT << 1 };
enemies.forEach(function (en) { if (!en.isJailed && en.y < bounds - 5 && Math.abs(en.x - this.x) < Math.abs(target.x - this.x) && Math.abs(en.y - this.y) < Math.abs(target.y - this.y)) target = en; }, this);
if (target.none)
{
    if (this.y < bounds - 5)
        return { "x": 0, "y": 2 };
    var speed = this.strength < 30 ? 1 : 2;
    if (this.x == 5 || this.x == WIDTH - 5)
        messages[this.id].dir = -messages[this.id].dir;
    return { "x": speed * messages[this.id].dir, "y": 0 };
}
if (this.x - target.x >= 0 && this.x - target.x < this.strength)
{
    if (this.y - target.y > 0 && this.y - target.y < this.strength + target.x - this.x)
        return { "x": this.x - target.x, "y": this.y - target.y };
    if (target.y - this.y > 0 && target.y - this.y < this.strength + target.x - this.x)
        return { "x": this.x - target.x, "y": target.y - this.y };
}
if (target.x - this.x > 0 && target.x - this.x < this.strength)
{
    if (this.y - target.y > 0 && this.y - target.y < this.strength + this.x - target.x)
        return { "x": target.x - this.x, "y": this.y - target.y };
    if (target.y - this.y > 0 && target.y - this.y < this.strength + this.x - target.x)
        return { "x": target.x - this.x, "y": this.y - target.y };
}
return { "x": 6 * sign(target.x - this.x), "y": 6 * sign(target.y - this.y) };

Defense bot.

\$\endgroup\$
  • 1
    \$\begingroup\$ Little thing about javascript -- you can't use this inside of a function (in your forEach loop). You have to save it as a variable beforehand (i.e. var _this = this;) and use _this. I'll add you as an exception if you edit soon because you thought that the controller wasn't loading your code and couldn't test. \$\endgroup\$ – soktinpk Apr 21 '15 at 21:07
  • \$\begingroup\$ @soktinpk Just used forEach's optional thisArg. \$\endgroup\$ – LegionMammal978 Apr 22 '15 at 10:13
  • \$\begingroup\$ I updated the controller to allow you even though you edited late. \$\endgroup\$ – soktinpk Apr 22 '15 at 12:21
1
\$\begingroup\$

Red - Flag Hunter

var distance = function(x1, y1, x2, y2) {
    return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
var moveTo = function(x, y, max) {
    if (max > this.strength)
        max = this.strength;
    var dX = x - this.x;
    var dY = y - this.y;
    var dist = distance(x, y, this.x, this.y);
    if (dist <= max) {
        return {x: dX, y: dY};
    }
    dX = dX * max / dist;
    dY = dY * max / dist;
    while (Math.sqrt(Math.abs(dX)+Math.abs(dY)) > max) {
        if (dX > dY) {
            dX = dX - 0.001;
        } else {
            dY = dY - 0.001;
        }
    }
    return {x: dX, y:dY};
}.bind(this);

var getSurroundingPoints = function(x, y, dist) {
    var points = [];
    for (var i = x - dist; i <= x + dist; i+= 0.2) {
        for (var j = y - dist; j <= y + dist; j+= 0.2) {
            if (i >= 0 && j >= 0 && j <= 180 && distance(i,j,x,y) <= dist) {
                points.push({x: i, y: j, danger: 0});
            }
        }
    }
    return points;
}

if (this.isJailed) {
    return {x:0, y:0};
}

var destination = {x: eFlag.x, y: eFlag.y}; //default: try to get the flag
if (eFlag.pickedUpBy != null) { //we got the flag
    if (eFlag.pickedUpBy.id == this.id) { //I got the flag => get back to the red side
        if (distance(this.x, this.y, this.x, 175.1) <= this.strength) {
            return moveTo(this.x, 175.1, this.strength);
        }
        destination.x = this.x;
        destination.y = 180;
    } else { //someone else got the flag => free those in the jail
        destination.x = 20;
        destination.y = 20;
    }
} else if (this.y > HEIGHT / 2) { //I am on the red side
    return moveTo(175, 175, 2);
} else if (distance(this.x, this.y, eFlag.x, eFlag.y) <= 15)  { //I am in the safe zone (flag)
    if (this.strength < 20)
        return {x:0, y:0};
    return moveTo(eFlag.x, eFlag.y, 2); //get the flag
} else if (distance(this.x, this.y, eFlag.x, eFlag.y) - this.strength <= 15)  { //I can reach the safe zone (flag)
    return moveTo(eFlag.x, eFlag.y, distance(this.x, this.y, eFlag.x, eFlag.y) - 14);
} else if (distance(this.x, this.y, 20, 20) < 10)  { //I am in the safe zone (jail)
    if (this.strength < 20)
        return {x:0, y:0};
} else if (distance(this.x, this.y, eFlag.x, eFlag.y) - this.strength <= 15)  { //I can reach the safe zone (jail)
    return moveTo(20, 20, this.strength);
}

//I am somewhere on the blue side
var points = getSurroundingPoints(this.x, this.y, this.strength);
var me = this;
points.forEach(function(point) {
    if (point.y < 175) {
        enemies.forEach(function(enemy) {
            if (distance(enemy.x, enemy.y, point.x, point.y) <= enemy.strength+10) {
                point.danger += 5;
            }
        });
        if (distance(me.x, me.y, point.x, point.y) <= 2 && point.danger == 0) {
            point.danger--;
        }
    }
});
var bestPoint = points[0];
points.forEach(function(point) {
    if (point.danger < bestPoint.danger || (point.danger == bestPoint.danger && distance(point.x, point.y, destination.x, destination.y) < distance(bestPoint.x, bestPoint.y, destination.x, destination.y))) {
        bestPoint = point;
    }
});
return moveTo(bestPoint.x, bestPoint.y, this.strength);

Tries to get the flag. If someone else already got it, Flag Hunter walks towards the jail, either confusing the enemy or freeing his team members.

\$\endgroup\$
1
\$\begingroup\$

Blue - A jolly good fellow

First attempt both at programming in Javascript, and at code-golf. It will chase anything that comes too close to the flag, trying to front-run their moves. Otherwise, it will run to rescue team-mates in jail, or lazily try to get to the other team's flag.

// Euclidean distance
var distance = function(p1,p2){
 return Math.sqrt( (p1.x - p2.x)**2 + (p1.y - p2.y)**2 );
}

// points from p1 to p2
var direction = function(p1, p2){
 return Math.atan2( p2.y - p1.y, p2.x - p1.x);
}

//moving
var move2 = function(dir, step){
    if(isNaN(dir)){   dir = 0; };
    if(isNaN(step)){ step = 0; };
    return {
        x: Math.cos(dir)*step,
        y: Math.sin(dir)*step
    };
}


var intercept_at = function(me, they, field){
    if ( distance(me, they)<me.strength ){
        return they;
    }

    //console.log("I am at (", me.x, me.y, ") ");
    //console.log("They are at (", they.x, they.y, ") ");           


    //first goal
    if( field.my_flag.pickedUpBy == null ){
        their_goal = field.my_flag;
        eta = (distance(they, their_goal) - they.strength)/2;
        their_dir = direction(they, their_goal);
        for( var i=1; i<eta; i++){
          they_next = {
            x: they.x + Math.cos(their_dir)*(they.strength/2 + (i+1)*2),
            y: they.y + Math.sin(their_dir)*(they.strength/2 + (i+1)*2)
          };
          if( (distance(me, they_next) )<(1.95*i) ){
            //console.log("goal is flag at (", their_goal.x, their_goal.y, ") ");   
            //console.log("I can reach it at (", they_next.x, they_next.y, ") in less than ", i);
            return they_next;
          }
        }
    // second goal  
    }

    my_flag = field.my_flag;
    their_goal = { x: my_flag.x, y:field.h/2 - 5};
    eta = (distance(my_flag, their_goal) - they.strength)/2;
    their_dir = direction(my_flag, their_goal);
    for( var i=0; i<eta; i++){
        they_next = {
            x: my_flag.x + Math.cos(their_dir)*(they.strength/2 + (i+1)*2),
            y: my_flag.y + Math.sin(their_dir)*(they.strength/2 + (i+1)*2)
        };
        if( (distance(me, they_next) )<(1.95*i) ){
            //console.log("goal is escaping at (", their_goal.x, their_goal.y, ") "); 
            //console.log("I can front-run it at (", they_next.x, they_next.y, ") in less than ", i);
            return they_next;
        }       
    }
    //console.log("Goose chase at (", they.x, they.y, ") ");
    return they;
}

var intercept = function(me, they, field){
  they_at = intercept_at(me, they, field);
  they_at.y = Math.min(they_at.y, field.h/2-5)
  dist2me = distance(me, they_at);
  dir2me = direction(me, they_at);  
  if ( dist2me<me.strength ){
    return move2(dir2me, dist2me);
  }else{
    return move2(dir2me, 2);
  }
}


var closest_enemy = function(my_flag, enemies){
    cur_tgt_num = null;
    cur_tgt_dst = 999;

    for( var i=0; i<enemies.length; i++){
      if(!enemies[i].isJailed){
        cur_dst = distance(enemies[i], my_flag);
        if ( cur_dst < cur_tgt_dst ){
          cur_tgt_dst = cur_dst;
          cur_tgt_num = i;
        }
      }
    }    

    return enemies[cur_tgt_num];
}


var enemies_closer_than = function(enemies, radius, field){
    closer_than = 0;
    for( var i = 0; i<enemies.length; i++){
        if(!enemies[i].isJailed){
            closer_than = closer_than + ( distance(enemies[i], field.my_flag)<radius );
        }
    }
    return closer_than;
}


var team_jailed = function(team){
    for( var i = 0; i<team.length; i++){
        if(team[i].isJailed){
            return team[i];
        }
    }
    return null;
}


var bound_positions = function(p0, field){
    p0.x = Math.max(0, Math.min(p0.x, field.w));
    p0.y = Math.max(0, Math.min(p0.y, field.h));    
    return p0;
}

var avoid_obstacle = function(me, goal, obstacle, field, can_run){
    //we know that there is a safe haven
    if( distance(me, goal)<me.strength && can_run){
        return move2(direction(me, goal), me.strength)
    }

    if( obstacle == null ){
        return move2(direction(me, goal), 2);
    }

    ob_dir = direction(me, obstacle);
    if( distance(me, obstacle) < Math.sqrt(2)*(4+obstacle.strength)){
        me_next1 = bound_positions({x: me.x - 4*Math.cos(ob_dir),y: me.y - 4*Math.sin(ob_dir)}, field);
        me_next2 = bound_positions({x: me.x - 4*Math.sin(ob_dir),y: me.y - 4*Math.cos(ob_dir)}, field);
        me_next3 = bound_positions({x: me.x - 4*Math.sin(ob_dir),y: me.y - 4*Math.cos(ob_dir)}, field);
        if( distance(goal, me_next1) < distance(goal, me_next2) ){
          if( distance(goal, me_next1) < distance(goal, me_next3) ){
            me_next = me_next1;
          }else{
            me_next = me_next3;
          }
        }else{
          if( distance(goal, me_next2) < distance(goal, me_next3) ){
            me_next = me_next2;
          }else{
            me_next = me_next3;
          }
        }           

        //console.log("Escaping from (", obstacle.x, obstacle.y, ")");
        return move2(direction(me, me_next), 4);
    }

    eta = (distance(me, goal)/2);
    my_dir = direction(me, goal);
    me_next = me;
    i=0;
    while(i<eta && (distance(me_next, obstacle) > obstacle.strength+2*i)){
       i++;
       me_next = {x: me_next.x + i*Math.cos(my_dir),y: me_next.y + i*Math.sin(my_dir)};
       me_next = bound_positions(me_next, field);
       if( distance(me_next, obstacle) < obstacle.strength+2*i ){
         me_next1 = {x: me_next.x + i*Math.sin(ob_dir),y: me_next.y - i*Math.cos(ob_dir)};
         me_next2 = {x: me_next.x - i*Math.sin(ob_dir),y: me_next.y + i*Math.cos(ob_dir)};
         if( distance(goal, me_next1) > distance(goal, me_next2) ){
            me_next = bound_positions(me_next2, field);
         }else{
            me_next = bound_positions(me_next1, field);
         }
       }
    }
    if( distance(me_next, obstacle) > obstacle.strength+2*i ){
        //console.log("Trying to reach goal at (", goal.x, goal.y, ") by pointing at (",me_next.x, me_next.y,")");      
        return move2(direction(me, me_next), 2);
    }

    //console.log("Waiting to reach (", goal.x, goal.y,")");
    my_dir = direction(me, goal);
    me_next = {
        x: me.x + Math.cos(my_dir)*6,
        y: me.y + Math.sin(my_dir)*6
    }
    for( var i=0; i<field.team.length; i++){
        me_next.x = me_next.x - Math.sign(field.team[i].x - me_next.x);
        me_next.y = me_next.y - Math.sign(field.team[i].y - me_next.y);
    }               
    return move2(direction(me, me_next), Math.floor(Math.random() * 2));
}


var field = {
    w: WIDTH, 
    h: HEIGHT, 
    g: DEFENSE_RADIUS,
    my_flag: tFlag,
    their_flag: eFlag,
    team: team,
    enemies: enemies
    };

var n_enemy = enemies.length;


  if( enemies_closer_than(enemies, field.h*0.67, field)>0 || tFlag.pickedUpBy !== null){
    //console.log("My flag is in danger");      

    // directive defend
    messages[123 + this.id] = 'defend_own_flag';
    if ( tFlag.pickedUpBy !== null ) {
      return intercept(this, tFlag.pickedUpBy, field);
    }else{
      return intercept(this, closest_enemy(tFlag, enemies), field);
    }   
  }else{

    if( tJailed.length>0 ){

        // directive support
        console.log("rescueing team member");
        console.log(tJailed);
        return avoid_obstacle(this, tJailed[0], closest_enemy(this, enemies), field, 0);
    }else{

      // directive attack
      messages[123 + this.id] = 'capture_enemy_flag';
      if ( eFlag.pickedUpBy == null ){
        //console.log("Going to capture the flag");
        return avoid_obstacle(this, eFlag, closest_enemy(this, enemies), field, 0)

      }else if (this.id == eFlag.pickedUpBy.id){
        //console.log("I have the flag");
        return avoid_obstacle(this, {x:this.x, y:field.h/2 - 1}, closest_enemy(this, enemies), field, 1);      

      }else {
        //console.log("Someone else has the flag");
        return avoid_obstacle(this, eFlag, closest_enemy(this, enemies), field, 0);

      }
    }
  }

return move2(0,0);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.