12
2
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 0tJailed
-- an array of all players on your team that are jailedeJailed
-- an array of all players on the opposing team that are jailedteam
-- an array of all players on your team, NOT just the ones near youenemies
-- an array of all players on the other team, NOT just the ones near youtFlag
-- 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
andy
strength
id
isJailed
-- true if the player is in jail
Every flag has the following properties:
x
andy
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
};
}
8
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)
– Calvin's Hobbies – 2015-04-20T03:19:06.740I agree with Calvin that you should make a second copy of this post as a meta question. – PhiNotPi – 2015-04-20T03:58:27.463
1
It would be halpful if you changed the controller link to http://jsfiddle.net/prankol57/4L7fdmkk/embedded/result/ for fullscreen.
– LegionMammal978 – 2015-04-20T10:08:58.027@Calvin'sHobbies Since I just copied the code from you, I'm pretty sure it will work (the ajax/obtaining answers stuff). I'm not so sure about the controller itself. – soktinpk – 2015-04-20T11:58:52.350
2Isn't the controller one of the most important parts...? – Alex A. – 2015-04-20T14:21:49.543
You should mention how
this
provides your own information. – LegionMammal978 – 2015-04-20T20:05:04.6301@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. – soktinpk – 2015-04-20T20:22:05.353
Also,
tJailed
should include the player currently being called. Otherwise, you'd have to do several calculations to determine if you're in jail. – LegionMammal978 – 2015-04-20T20:48:43.993So, is this challenge ready to start? Can I post answers? – ASCIIThenANSI – 2015-04-20T22:26:33.170
1My bot isn't appearing on the controller. – LegionMammal978 – 2015-04-21T10:13:15.560
@soktinpk We aren't talking about posting it in the sandbox, we're talking about posting is as a sandbox, a separate question which would allow you to test the controller. – PhiNotPi – 2015-04-21T10:35:11.370
@LegionMammal978 Yeah it is! It seems to be working. (Just press new game to load everything) – soktinpk – 2015-04-21T12:14:47.697
@PhiNotPi I know. But I'm using the exact same code as Calvin'sHobbies so why wouldn't it work? I thought opening a new question for already-tested code is sort of a waste... – soktinpk – 2015-04-21T12:22:46.417
1@soktinpk But you aren't using my exact code. The answer loading and interface may be essentially the same (though at second glance, the interface really isn't) but the part that actually plays the game is different. People are more likely to spend time writing answers if they can see that some testing has been done and can trust that you will properly manage the contest. (I personally don't have these feelings of trust if only because the background of the blue team box is unaccountably pink.) – Calvin's Hobbies – 2015-04-22T16:06:51.833
From running some games with the current submissions, it seems that a lot of games get into a stalemate situation. How are those handled in the scoring? Calvin's contest had a hard time limit, after which a tiebreaker condition was used. – DLosc – 2015-04-23T06:58:58.393
1You should make it so that when someone frees their teammates from jail, they also get teleported back to their side. This would prevent Lazy Jail Hogs. – LegionMammal978 – 2015-04-25T00:40:44.560
This looks fun, but I wish it wasn't limited to JavaScript. – NobodyNada - Reinstate Monica – 2015-05-18T20:23:46.177