134
\$\begingroup\$

This contest is officially over. The Blue Team won!

I autoran two sets of 50 battles and amazingly, Blue won all 100 of them. Looking at the stats, it is clear that the cooperative entries of PhiNotPi and Sp3000 were the real heroes. Great job you two! In fact, if you disqualify every other member of the Blue Team, the Sphibots still put up a very good fight. Some Red Team folks were planning to take down the Sphibots, but this effort seemed to peter out. Sorry Red Team.

The contest is officially over, but that does not mean you can no longer answer, it only means that I won't ever redeclare the official winner. Both teams are welcome to keep submitting bots, just for fun. The controller will stay up and remain functional as long as no future entries break it.


This is a king-of-the-hill contest, but instead of everyone fighting against each other, there will be two teams that compete: Red and Blue. Only one will be the winner.

The team you are on depends on your PPCG user ID number. To find this, click your avatar at the top of the screen (you must be logged in) and look at the url of the page that opens. The number after users/ is your ID number:

https://codegolf.stackexchange.com/users/[id number]/[display name]

For example, my PPCG user ID number is 26997:

https://codegolf.stackexchange.com/users/26997/calvins-hobbies

Note that this number is different for different Stack Exchange sites.

If your ID is an even number, then you are on the Red team.
If your ID is an odd number, then you are on the Blue team.
There is no way to change teams.

You must work with your team to try to defeat the other team in a sort of battle royal where every user controls a "pixel" of their team's color on the 128×128 grid that is the battlefield. Pixels can move around, communicate with their teammates, and take out the other team's pixels. It would get out of hand if anyone could create any number of pixels, so every user may only submit one answer to this question.

This Stack Snippet (a minified version of this fiddle [fullscreen]) is the controller for the entire contest. It automatically reads the submissions, makes sure they are valid, and stages battles between the teams. It does this right in your browser at any time you want, using JavaScript. Since JavaScript is the only client-side scripting language most browsers support, all submissions must be written in JavaScript as well.

function toggleDebug(){debug=$("#debug").is(":checked")}function rnd(e){return Math.floor(Math.random()*e)}function shuffle(e){for(var t,a,r=e.length;r;t=rnd(r),a=e[--r],e[r]=e[t],e[t]=a);return e}function maskedEval(e,t){var a={};for(i in this)a[i]=void 0;for(i in t)t.hasOwnProperty(i)&&(a[i]=t[i]);return new Function("with(this) { "+e+";}").call(a)}function createBattle(e,t,a,r){function n(){var e=rnd(i.length),t=i[e];return i.splice(e,1),t}var l={};l.width=l.height=128,l.totalMoves=2048,l.radius=16,l.msgMaxLength=64,l.timeLimit=15,l.move=0,l.redToMove=a,l.animated=r,l.running=!1,l.over=!1;for(var o=0,i=new Array(l.width*l.height),d=0;d<l.height;d++)for(var s=0;s<l.width;s++)i[o++]={x:s,y:d};l.redTeam=shuffle(e.slice()),l.redMsgs={},l.redKills={};for(var o=0;o<l.redTeam.length;o++){var u=n();l.redTeam[o].x=u.x,l.redTeam[o].y=u.y,l.redMsgs[l.redTeam[o].id]="",l.redKills[l.redTeam[o].id]=0}l.blueTeam=shuffle(t.slice()),l.blueMsgs={},l.blueKills={};for(var o=0;o<l.blueTeam.length;o++){var u=n();l.blueTeam[o].x=u.x,l.blueTeam[o].y=u.y,l.blueMsgs[l.blueTeam[o].id]="",l.blueKills[l.blueTeam[o].id]=0}return l}function drawBattle(e){function t(e){var t=3*e.x,a=3*e.y;ctx.fillRect(t,a,3,3),showNames.is(":checked")&&ctx.fillText(e.title,t+5,a+12)}function a(t){ctx.beginPath(),ctx.arc(3*t.x,3*t.y,3*e.radius,0,2*Math.PI),ctx.closePath(),ctx.fill()}e.animated&&(ctx.clearRect(0,0,canvas.width,canvas.height),showCircles.is(":checked")&&(ctx.fillStyle="rgba(255, 0, 0, 0.1)",e.redTeam.forEach(a),ctx.fillStyle="rgba(0, 0, 255, 0.1)",e.blueTeam.forEach(a)),ctx.fillStyle="red",e.redTeam.forEach(t),ctx.fillStyle="blue",e.blueTeam.forEach(t),moveCounter.text((e.move+1).toString()))}function movePlayer(e,t,a,r,n,l,o,i){function d(a){t.id!==a.id&&Math.sqrt(Math.pow(t.x-a.x,2)+Math.pow(t.y-a.y,2))<e.radius&&(u.push({x:a.x,y:a.y,id:a.id}),debug&&console.log(a.title+" is near"))}debug&&(console.log("--- Moving "+t.title+" ---"),console.log("position before move = ("+t.x.toString()+", "+t.y.toString()+")"));var s={};s.move=a,s.x=t.x,s.y=t.y,s.tCount=r.length,s.eCount=n.length,s.setMsg=function(a){"string"==typeof a&&(l[t.id]=a.length>e.msgMaxLength?a.substring(0,e.msgMaxLength):a,debug&&console.log('set message to "'+l[t.id]+'"'))},s.getMsg=function(e){var t=l.hasOwnProperty(e)?l[e]:void 0;return debug&&console.log('got message "'+t+'" from player with id '+e.toString()),t};var u=[];r.forEach(d),s.tNear=u,u=[],n.forEach(d),s.eNear=u,-1===t.id&&(s.console=console);var c=0,g=performance.now();try{c=maskedEval(t.code,s)}catch(v){c=0,debug&&(console.log("encountered error:"),console.log(v))}g=performance.now()-g,debug&&console.log("time taken = "+g.toString()+"ms"),g>e.timeLimit&&(c=0,debug&&console.log("went over the time limit of "+e.timeLimit+"ms"));var m=t.x,h=t.y;switch(c){case 1:e.redToMove?++m:++h;break;case 2:e.redToMove?--m:--h;break;case 3:++m,--h;break;case 4:--m,--h;break;case 5:--m,++h;break;case 6:++m,++h}m>=0&&m<e.width&&h>=0&&h<e.height&&(t.x=m,t.y=h),debug&&console.log("move direction = "+c);for(var f=0;f<n.length;f++)t.x===n[f].x&&t.y===n[f].y&&(debug&&console.log("took out "+n[f].title),++i[t.id],o[n[f].id]="X",n.splice(f--,1))}function advanceBattle(e){debug&&console.log("====== "+(e.redToMove?"Red ":"Blue ")+e.move.toString()+" ======");var t,a,r,n,l;e.redToMove?(t=e.redTeam,a=e.blueTeam,r=e.redMsgs,n=e.blueMsgs,l=e.redKills):(t=e.blueTeam,a=e.redTeam,r=e.blueMsgs,n=e.redMsgs,l=e.blueKills),t.forEach(function(o){movePlayer(e,o,Math.floor(e.move/2)+1,t,a,r,n,l)}),drawBattle(e);var o;return 0===a.length?(o=e.redToMove?1:-1,e.over=!0):++e.move>=e.totalMoves&&(o=e.redTeam.length>e.blueTeam.length?1:e.redTeam.length<e.blueTeam.length?-1:0,e.over=!0),e.redToMove=!e.redToMove,debug&&"undefined"!=typeof o&&console.log("win status = "+o.toString()),o}function newBattle(){if(0===redTeam.length||0===blueTeam.length)return void alert("Each team must have at least one player.");"undefined"!=typeof interval&&clearInterval(interval);var e=parseInt($("#delay").val());return isNaN(e)||0>e?void alert("Delay must be a non-negative integer."):(debug&&console.log("Created new battle with delay "+e.toString()),battle=createBattle(redTeam,blueTeam,$("#redMovesFirst").is(":checked"),!0),drawBattle(battle),void moveCounter.text("0").css("color","black"))}function reportKills(e,t){for(var a="Red Kills:\n",r=0;r<redTeam.length;r++)a+=e[redTeam[r].id].toString()+" by "+redTeam[r].title+"\n";a+="\nBlue Kills:\n";for(var r=0;r<blueTeam.length;r++)a+=t[blueTeam[r].id].toString()+" by "+blueTeam[r].title+"\n";return a}function intervalCallback(){var e=advanceBattle(battle);"undefined"!=typeof e&&(clearInterval(interval),battle.running=!1,alert([0===e?"Tie!":e>0?"Red Wins!":"Blue Wins!","Red remaining: "+battle.redTeam.length,"Blue remaining: "+battle.blueTeam.length,"\n"].join("\n")+reportKills(battle.redKills,battle.blueKills)))}function run(){if("undefined"!=typeof battle&&!battle.running&&!battle.over){battle.running=!0;var e=parseInt($("#delay").val());if(isNaN(e)||0>e)return void alert("Delay must be a non-negative integer.");interval=setInterval(intervalCallback,e)}}function pause(){"undefined"!=typeof battle&&(battle.running=!1),"undefined"!=typeof interval&&clearInterval(interval)}function step(){"undefined"==typeof battle||battle.running||battle.over||intervalCallback()}function autorunBattles(){function e(e){for(var t,i=createBattle(redTeam,blueTeam,e,!1);!i.over;)if(t=advanceBattle(i),"undefined"!=typeof t){i.over=!0,1===t?++a:-1===t?++n:++r;for(var d in i.redKills)i.redKills.hasOwnProperty(d)&&(l[d]+=i.redKills[d]);for(var d in i.blueKills)i.blueKills.hasOwnProperty(d)&&(o[d]+=i.blueKills[d])}}if(pause(),battle=void 0,0===redTeam.length||0===blueTeam.length)return void alert("Each team must have at least one player.");var t=parseInt($("#N").val());if(isNaN(t)||0>t)return void alert("N must be a non-negative integer.");console.log("Autorunning "+t.toString()+" battles");for(var a=0,r=0,n=0,l={},o={},i=0;i<redTeam.length;i++)l[redTeam[i].id]=0;for(var i=0;i<blueTeam.length;i++)o[blueTeam[i].id]=0;for(var i=0;t>i;i++)console.log("Battle "+i.toString()),e(i%2===0);alert([a===n?"Tie overall!":a>n?"Red wins overall!":"Blue wins overall!","Red wins: "+a.toString(),"Blue wins: "+n.toString(),"Ties: "+r.toString(),"\n"].join("\n")+reportKills(l,o))}function changeSelect(e){var t=e?redTeam:blueTeam,a=$(e?"#redSelect":"#blueSelect").val(),r=$(e?"#redCode":"#blueCode"),n=$(e?"#redLink":"#blueLink");null!==a&&a>-1?(r.text(t[a].code),n.attr("href",t[a].link)):(r.text(""),n.attr("href","javascript:;"))}function loadEntries(){function e(e,t){url="https://api.stackexchange.com/2.2/questions/"+qid.toString()+"/answers?page="+e.toString()+"&pagesize=100&order=asc&sort=creation&site=codegolf&filter=!JDuPcYJfXobC6I9Y-*EgYWAe3jP_HxmEee",$.get(url,t)}function t(d){d.items.forEach(function(e){function t(e,t){t.append(" ").append($("<a>").text(e.owner.display_name).attr("href",e.link))}function n(e){return $("<textarea>").html(e).text()}var d=e.owner.user_id%2===0,s=d?redTeam:blueTeam;if(e.owner.display_name=n(e.owner.display_name),e.hasOwnProperty("last_edit_date")&&e.last_edit_date-e.creation_date>r||dq.indexOf(e.owner.user_id)>-1||l.indexOf(e.owner.user_id)>-1)return void t(e,o);l.push(e.owner.user_id);var u=a.exec(e.body);if(null===u||u.length<=1)return void t(e,i);var c={};c.id=e.owner.user_id,c.title=e.owner.display_name+" ["+e.owner.user_id.toString()+"]",c.code=n(u[1]),c.link=e.link;var g=$(d?"#redSelect":"#blueSelect");g.append($("<option>").text(c.title).val(s.length)),s.push(c)}),d.has_more?e(++n,t):($("#loadStatus").hide(),$("#redCount").text(redTeam.length.toString()),$("#blueCount").text(blueTeam.length.toString()),0===o.html().length&&o.html(" none"),0===i.html().length&&i.html(" none"))}var a=/<pre><code>((?:\n|.)*?)\n<\/code><\/pre>/,r=28800,n=1,l=[],o=$("#disqualified"),i=$("#invalid");pause(),battle=void 0,redTeam=[],blueTeam=[],$("#loadStatus").show(),$("#redSelect").empty(),$("#redCode").empty(),$("#redLink").attr("href","javascript:;"),$("#blueSelect").empty(),$("#blueCode").empty(),$("#blueLink").attr("href","javascript:;");var d=$("#testbot").val();if(d.length>0){debug&&console.log("Using test entry");var s={id:-1,title:"TEST ENTRY [-1]",link:"javascript:;",code:d};$("#testbotIsRed").is(":checked")?(redTeam.push(s),$("#redSelect").append($("<option>").text(s.title).val(0))):(blueTeam.push(s),$("#blueSelect").append($("<option>").text(s.title).val(0)))}e(1,t)}var qid=48353,dq=[],ctx,moveCounter,showNames,showCircles,debug=!1,battle,redTeam,blueTeam,interval;$(document).ready(function(){ctx=$("#canvas")[0].getContext("2d"),moveCounter=$("#moveCounter"),showNames=$("#showNames"),showCircles=$("#showCircles"),loadEntries()});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><style>html *{font-family: Consolas, Arial, sans-serif;}select{width: 100%; margin: 12px 0 0 0;}button, select, input{font-size: 100%;}input{text-align: right;}textarea{font-family: "Courier New", monospace;}textarea[readonly]{background-color: #eee; width: 100%;}canvas{margin: 12px 0 0 0; border: 2px solid gray;}.redWrapper, .blueWrapper{width: 30%;}.redWrapper{float: left;}.blueWrapper{float: right;}.arenaWrapper{width: 40%; display: inline-block;}.redTeam, .blueTeam, .arena{padding: 12px;}.arena{text-align: center;}.redTeam, .blueTeam{border-style: solid; border-width: medium;}.redTeam{border-color: red; background-color: #fee;}.blueTeam{border-color: blue; background-color: #eef;}.redTitle, .blueTitle, .arenaTitle{text-align: center; font-size: 200%;}.redTitle, .blueTitle{font-weight: bold;}.redTitle{color: red;}.blueTitle{color: blue;}.control{margin: 12px 0 0 0;}.count{font-size: 75%; margin: 0 0 12px 0;}.footnotes{font-size: 75%; clear: both; padding: 12px;}</style><div id='loadStatus'> Loading entries...</div><div> <div class='redWrapper'> <div class='redTeam'> <div class='redTitle'> Red Team </div><select id='redSelect' size='20' onchange='changeSelect(true)'> </select> <div class='count'> <span id='redCount'></span> players </div>Code: <br><textarea id='redCode' rows='12' readonly></textarea> <br><a id='redLink' href='javascript:;'> Answer Link </a> </div></div><div class='arenaWrapper'> <div class='arena'> <div class='arenaTitle'> Battlefield </div><canvas id='canvas' width='384' height='384'> Your browser does not support the canvas tag. </canvas> <div>Move <span id='moveCounter'>0</span></div><br><div> <div class='control'> <input id='showNames' type='checkbox'>show names <input id='showCircles' type='checkbox'>show circles </div><div class='control'> <input id='redMovesFirst' type='checkbox'>red moves first </div><div class='control'> <input id='delay' type='text' size='4' value='20'> millisecond delay </div><div class='control'> <button type='button' onclick='newBattle()'> New Battle </button> <button type='button' onclick='run()'> Run </button> <button type='button' onclick='pause()'> Pause </button> <button type='button' onclick='step()'> Step </button> </div><hr class='control'> <div class='control'> <button type='button' onclick='autorunBattles()'> Autorun N Battles </button> N&nbsp;=&nbsp;<input id='N' type='text' size='4' value='16'> </div><div class='footnotes'> Autoruns may hang browser tab until complete. </div></div></div></div><div class='blueWrapper'> <div class='blueTeam'> <div class='blueTitle'> Blue Team </div><select id='blueSelect' size='20' onchange='changeSelect(false)'> </select> <div class='count'> <span id='blueCount'></span> players </div>Code: <br><textarea id='blueCode' rows='12' readonly></textarea> <br><a id='blueLink' href='javascript:;'> Answer Link </a> </div></div></div><div class='footnotes'> Test Entry: (id&nbsp;=&nbsp;-1) <input id='testbotIsRed' type='checkbox'>On Red Team <br><textarea id='testbot' rows='1' cols='32'></textarea> <br><button type='button' onclick='loadEntries()'> Reload with test entry </button> <br><br>This was designed and tested in Google Chrome. It might not work in other browsers. <br>Disqualified entries:<span id='disqualified'></span> <br>Could not find code block:<span id='invalid'></span> <br><input id='debug' type='checkbox' onclick='toggleDebug()'>Debug messages <br></div>

For visibility, the Snippet's battlefield is scaled by a factor of 3, so it's 384×384 real pixels and the "pixels" are 3×3.

Pixel Team Battlebots - Overview

Players

Each valid answer to this question represents a player. (For details on validity, see "Rules and Disqualifications".) Every player has control over a single 1×1 cell (a.k.a. pixel) on the 128×128 cell battlefield. Players on the Red team have red pixels and players on the Blue team have blue pixels.

Battles

A battle is a fight between all the players on both teams, even if the teams don't have an equal number of players. A battle starts with every player being placed at a random position in the battlefield, that is, any integer coordinate from (0,0) at the top left, to (127,127) at the bottom right. It is guaranteed that no two players will start in the same position.

Moves

Each battle is broken up into 2048 moves. Only one team actually gets to move their players during each move. That team alternates back and forth from Red to Blue, so each team makes 1024 moves total (unless the game ends early).

The team that gets to move first is an option you must set in the controller.
When battles are autorun, the team who moves first alternates in every battle.

Player Moves

When a team moves, all the players on that team are called on to move themselves. These calls are done in a completely random order for each move. When called, each player is given data about the state of the battle so they can decide which way to move.

All moves are only up to one pixel away. The dark circles in these diagrams mark which positions each colored player (the squares) can move to:

Red team moves diagram Blue team moves diagram

Both colors can move diagonally in any direction or stay still, but only Red players can move right and left, and only Blue players can move down and up. Thanks Phi and others.

If a player tries to move out of the battlefield bounds, or takes too long deciding which way to move, or has some sort of error, they will automatically stay still.

In addition to moving, during a turn a player can read messages written by their teammates and write messages that may in turn be read. This allows for a crude form of team communication.

The code that you submit as an answer is the logic that determines which way to move your player and what messages to read and write (see "How To Answer").

Removing Enemy Players

When a player moves into the same cell as a player on the opposing team, that opposing player is immediately removed from the battle. The player that just moved continues on as normal. This is the only mechanism that removes players from the battle and mastering it is the key to winning!

If there are multiple enemy players in the cell a player just moved to, then all the enemy players are removed. Nothing happens if two players on the same team occupy the same cell.

Winning a Battle

A battle ends once all 2048 moves have been made or when one team has no players left. The team with the largest number of surviving players wins. It's a tie If both teams have an equal number of surviving players.

How To Answer

In your answer, you need to provide the JavaScript code that decides which way your pixel will move when called on to do so.

In the first indented code sample in your answer (the ones prefixed with 4 spaces), write a body for this function:

function moveMe(move, x, y, tCount, eCount, tNear, eNear, setMsg, getMsg) {
    //the body goes here
}

There is no need to golf your code.

What to Return

The return value of the function determines which way your pixel moves:

0 to stay still
1 to move right for the Red team, down for the Blue team
2 to move left for the Red team, up for the Blue team
3 to move diagonally up and right
4 to move diagonally up and left
5 to move diagonally down and left
6 to move diagonally down and right

As a diagram:

move return values diagram

Your pixel will stay still by default if your code does any of these things:

  • Returns anything besides an integer from 0 to 6.
  • Attempts to move pixel out of the battlefield bounds.
  • Takes longer than 15 milliseconds to run.
  • Throws any sort of exception.

Your entry does not need to be deterministic; using Math.random is fine.

The Parameters

The first 7 function parameters of moveMe give information about the state of the battle:

  • move is an integer that starts at 1 and increments after every move until it is 1024 on your team's last move.
  • x is your current x position, an integer from 0 (leftmost) to 127 (rightmost).
  • y is your current y position, an integer from 0 (topmost) to 127 (bottommost).
  • tCount is the current total number of surviving players on your team.
  • eCount is the current total number of surviving players on the enemy team.
  • tNear is a list of the current surviving players on your team that are less than 16 pixels away (Euclidean distance). Each element of tNear is an object with x, y, and id properties:
    • x is the x position of the other player
    • y is the y position of the other player
    • id is the PPCG user ID number of the other player (as an integer)
  • eNear is exactly like tNear except it is a list of nearby enemy players, not teammates.

The circles in the snippet are each player's tNear and eNear range.

Messages

The last 2 parameters, setMsg and getMsg, have slightly different purposes.

Throughout a battle, each player has a string of up to 64 characters that they can manipulate during every move to store data and potentially communicate with their teammates. Each player's string starts out as the empty string. When a player is removed from the battle, their string is set to "X".

  • setMsg is a one argument function that sets your string to the string passed in.
    • If the value passed in is not a string, then your string doesn't change.
    • If the value is a string with more than 64 characters, only the first 64 are kept.
  • getMsg is a one argument function that takes the PPCG user ID number (as an integer) of someone on your team and returns their string.
    • That player may be anywhere in the grid. They do not need to be in your 16 pixel radius.
    • undefined is returned if the given ID is not found.

Example Submission

This player moves up and right if there is an enemy to the left, or else down and left if teammate with ID 123 says to, but otherwise stays still:

for (var i = 0; i < eNear.length; i++) {
    if (eNear[i].x === x - 1)
        return 3
}
if (getMsg(123) === 'move down and left')
    return 5
return 0

Note that this code block is all that's required. The function definition and brackets should not be present.

Rules and Disqualifications

If a user is not abiding by the rules listed below, I may mark them as disqualified and the controller will automatically ignore their answers. I trust that most users here won't intentionally break the rules and there will only be a few temporary disqualifications for accidental causes.

Important Rules

  1. You may only edit your answer during the 8 hour window directly after posting it.
    Answers that are edited after 8 hours from when they were posted will automatically be disqualified by the controller. This rule is to prevent initial answers from continually optimizing their approach, possibly stealing ideas from later answers. Your team has to make do with whatever answers it started out with.

    You may not delete and repost your answer without special permission. I will give this if someone inadvertently edits your post after the 8 hour mark or something like that, but not just because you found a bug.

    If you delete your post and choose to undelete it, the edit rule still applies. (The controller cannot see deleted answers.)

  2. When declaring a new JavaScript variable, you must use the var keyword.
    This is because a variable declared without var becomes global rather than local, so it would be easy to accidentally (or intentionally) mess with the controller or communicate freely with other players. It has to be clear that you aren't trying to cheat.

    When declaring functions it is best to use the var keyword as well. i.e. use var f = function(...) {...} instead of function f(...) {...}. I'm not entirely sure why, but sometimes it appears to make a difference.

  3. Your code should not run for an excessive amount of time.
    If your code takes more than 15 milliseconds to run, your pixel will not move at all. However, since it is difficult in JavaScript to stop functions mid-execution, all player scripts are run to completion on every move, and the time checked afterward. This means, if your code does some time intensive thing, everyone running the controller will notice and be annoyed.

Automatic Disqualifications

The controller automatically disqualified entries for these reasons:

  • The user has already answered.
  • Edits were made more than 8 hours after creation.
  • User is specifically marked as disqualified.

Other Rules

In your code you may not...

  • attempt to access or modify the controller or other player's code.
  • attempt to modify anything built into JavaScript.
  • attempt to communicate with other players except by using getMsg and setMsg.
  • make web queries.
  • do otherwise malicious things.

I'll keep an eye out for other unsportsmanlike behavior, such as stealing code verbatim from other answers or using sock puppets to mess with the other team.

You are welcome to collaborate and scheme with your team, but keep the contest friendly and ethical.

If you think someone needs to be disqualified or think you fixed the reason you were disqualified, leave a comment here for me or in the question specific chat. I am not participating in the contest.

Suggested Answer Format

#[team color] Team - [entry title]

    //function body
    //probably on multiple lines

Explanations, notes, etc.

The entry title is an optional name you can give if you like. The controller does not do anything with it.

Scoring

This contest will be officially over on April 19, 2015. On that day (around 11 pm UTC) I will autorun at least 100 battles (possibly many more depending on how long battles take). The team that wins the most will be the overall winner. If it is a tie or extremely close, I will run more battles until it is clear that one team has the advantage.

(You may answer after the winner is decided, but I won't be changing the official outcome.)

I'll be running them in the latest version of Google Chrome on a laptop with Windows 8.1 64-bit, 4 GB ram, and a 1.6GHz quad core processor. Make sure your JavaScript works in Chrome.

The victory is primarily about team glory, but I will accept the highest voted answer on the winning team.

Throughout the contest, bear in mind that the team based aspect, and the fact that it is run entirely in a Stack Snippet, are very experimental. I have high hopes, but I can't say for sure how well things will work.

Tips:

  • You can test entries before answering. Edit the "Test Entry" textbox near the bottom of the Stack Snippet and click "Reload with test entry". If it is not empty it becomes a player on the team specified.
  • Answers are run in a masked scope, so things like alert and console.log won't work. The console object can only be used in the test entry.
  • Check "Debug messages" at the bottom of the Stack Snippet and look at your browser's console (F12). Lot's of useful info is printed when battles are running.
  • You can use the Meta Sandbox post as a kind of staging area. The answers there are of course different than here, and the controller there may get out of date.
  • Since this is not an official Stack App, the controller may stop loading answers for you if you restart it more than 300 times in a day.

This challenge's "sequel": Block Building Bot Flocks!

Quick Links

Fiddle Controller   Fullscreen   General Chat   Red Chat   (Blue Chat?)   SandboxPost

\$\endgroup\$
32
  • 53
    \$\begingroup\$ You did a really awesome job putting this all together. \$\endgroup\$
    – Alex A.
    Commented Apr 2, 2015 at 4:34
  • 6
    \$\begingroup\$ Everyone, please use chat for non-crucial comments. \$\endgroup\$ Commented Apr 2, 2015 at 17:26
  • 4
    \$\begingroup\$ The red team is coordinating in this chat-room: chat.stackexchange.com/rooms/22548/… \$\endgroup\$
    – Adam Davis
    Commented Apr 3, 2015 at 17:00
  • 5
    \$\begingroup\$ Help me, I'm absolutely in love with this. Please spread the word to every single developer you know. \$\endgroup\$ Commented Apr 4, 2015 at 21:41
  • 5
    \$\begingroup\$ Ironically, PhiNotPi and Sp3000's bots are now auto-disqualified by the official controller script, since they've been edited by Community♦ after the 8 hour deadline. For anyone who wants to see how they perform together, here's a hacked version of the controller script with the edit time limit removed. \$\endgroup\$ Commented Jul 16, 2017 at 21:23

22 Answers 22

52
+500
\$\begingroup\$

Blue Team - SphiNotPi3000

// Char 0: top or bottom ("T" or "B")
// Char 1, 2: x/y coords
// Char 3, move polarity
// Char 4: offset (as codepoint - 128)

var twin = 21487;
var myself = 2867;
var formpos = "T";

var tochar = String.fromCharCode;
var movestat = (move % 2).toString();

var inbox = getMsg(twin);

// Spoofing the message of a deceased partner
if (inbox == "X"){
    inbox = "B" + tochar(x) + tochar(y+1) + ((move + 1) % 2).toString() + tochar(0);
}

var selfsafe = [9,10,10,10,10,10,10];

// Remove useless edge moves
if (x == 0){
    selfsafe[4] = 0;
    selfsafe[5] = 0;
}
if (x == 127){
    selfsafe[3] = 0;
    selfsafe[6] = 0;
}
if (y == 0){
    selfsafe[2] = 0;
    selfsafe[3] = 0;
    selfsafe[4] = 0;
}
if (y == 127){
    selfsafe[1] = 0;
    selfsafe[6] = 0;
    selfsafe[5] = 0;
}

var selfdisp = [[0,0],[0,1],[0,-1],[1,-1],[-1,-1],[-1,1],[1,1]];

if (inbox == "") {
    // First move, pick anywhere safe

    for (j = 0; j < 7; j++) {
        for (var i = 0; i < eNear.length; i++){
            var enemy = eNear[i];
            var dx = enemy.x - x - selfdisp[j][0];
            var dy = enemy.y - y - selfdisp[j][1];

            if (dx * dx == 1 && dy >= -1 && dy <= 1) {
                selfsafe[j] = 0;
            }
        }

        if (selfsafe[j]) {
            var strpos = tochar(x + selfdisp[j][0]) + tochar(y + selfdisp[j][0]);
            var offset = tochar(Math.floor(Math.random() * 256));
            setMsg(formpos + strpos + movestat + offset);
            return j;
        }
    }

} else {
    var twinformpos = inbox.charAt(0);
    var twinx = inbox.charAt(1).charCodeAt();
    var twiny = inbox.charAt(2).charCodeAt();
    var twinmovestat = inbox.charAt(3);
    var offset = inbox.charAt(4);

    formpos = twinformpos == "T" ? "B" : "T";
    var targetx = twinx;
    var targety = formpos == "T" ? (twiny - 1) : (twiny + 1);

    // If true, then this bot is either the second one to move or is not in position.  Move into position.
    if (twinmovestat == movestat || x != targetx || y != targety) {
        var bestmove = 0;

        for (var j = 0; j < 7; j++) {
            for (var i = 0; i < eNear.length; i++){
                var enemy = eNear[i];
                var dx = enemy.x - x - selfdisp[j][0];
                var dy = enemy.y - y - selfdisp[j][1];

                if (dx * dx == 1 && dy >= -1 && dy <= 1) {
                    selfsafe[j] = 0;
                }
                if (dx == 0 && dy == 0){
                    selfsafe[j] *= 2;
                }
            }

            selfsafe[j] -= Math.abs(x + selfdisp[j][0] - targetx) + Math.abs(y + selfdisp[j][1] - targety);

            if (selfsafe[j] > selfsafe[bestmove]) {
                bestmove = j;
            }
        }

        var strpos = tochar(x + selfdisp[bestmove][0]) + tochar(y + selfdisp[bestmove][1]);
        setMsg(formpos + strpos + movestat + offset);
        return bestmove;

    } else {
        // In formation, and is the leader this turn

        var topy = formpos == "T" ? y : (y - 1);
        var topx = x;
        var safe = [1,1,1,1,1,1,1,1,1];
        var disp = [[0,0],[0,1],[0,-1],[1,-1],[-1,-1],[-1,1],[1,1],[1,0],[-1,0]];
        var otherpos = formpos == "T" ? "B" : "T";

        // Avoid dangerous squares and always kill if safe to do so
        for (var j = 0; j < 9; j++){
            var ntopx = topx + disp[j][0];
            var ntopy = topy + disp[j][1];

            if (ntopx < 0 || ntopx > 127 || ntopy < 0 || ntopy > 126){
                safe[j] = 0;
                continue;
            }

            for (var i = 0; i < eNear.length; i++){
                var enemy = eNear[i];
                var dx = enemy.x - ntopx;
                var dy = enemy.y - ntopy;

                if(dx * dx == 1 && dy >= -1 && dy <= 2){
                    safe[j] = 0;
                    continue;
                }

                if(dx == 0 && dy >= 0 && dy <= 1){
                    // Kill!
                    var strpos = tochar(x + disp[j][0]) + tochar(y + disp[j][1]);

                    if (j > 6) {
                        setMsg(otherpos + strpos + movestat + offset);
                        if (formpos == "T"){return 13 - j;}
                        return j - 4;
                    }

                    setMsg(formpos + strpos + movestat + offset);
                    return j;
                }
            }
        }

        var pref = [];

        for (var i = 0; i < eNear.length; i++){
            var enemy = eNear[i];
            var dy = enemy.y - topy;
            var dx = enemy.x - topx;

            if (dy < 0 && dx == 0){ pref=[2,4,3,8,7,1,5,6,0]; }
            if (dy > 0 && dx == 0){ pref=[1,5,6,7,8,2,4,3,0]; }
            if (dy == 0 && dx > 0){ pref=[7,6,3,1,2,5,4,8,0]; }
            if (dy == 0 && dx < 0){ pref=[8,5,4,1,2,6,3,7,0]; }
            if (dy < 0 && dx < 0){ pref=[4,8,5,1,0,2,6,7,3]; }
            if (dy > 0 && dx < 0){ pref=[5,8,4,2,0,1,3,7,6]; }
            if (dy < 0 && dx > 0){ pref=[3,7,6,1,0,2,5,8,4]; }
            if (dy > 0 && dx > 0){ pref=[6,7,3,2,0,1,4,8,5]; }

            for (var k = 0; k < pref.length; k++)
            {
                if (safe[pref[k]]){
                    var strpos = tochar(x + disp[pref[k]][0]) + tochar(y + disp[pref[k]][1]);

                    if (pref[k] > 6) {
                        setMsg(otherpos + strpos + movestat + offset);
                        if(formpos == "T"){return 13 - pref[k];}
                        return pref[k] - 4;
                    }

                    setMsg(formpos + strpos + movestat + offset);
                    return pref[k];
                }
            }
        }

        var offsetint = offset.charCodeAt();
        var offsetmove = move - 128 + offsetint;

        if (offsetmove % 900 < 30) {
            var targetx = 64 - (offsetmove % 30);
            var targety = 64 - (offsetmove % 30);
        } else if (offsetmove % 900 < 90) {
            var targetx = 34 + ((offsetmove - 30) % 60);
            var targety = 34;
        } else if (offsetmove % 900 < 150) {
            var targetx = 94;
            var targety = 34 + ((offsetmove - 30) % 60);
        } else if (offsetmove % 900 < 210) {
            var targetx = 94 - ((offsetmove - 30) % 60);
            var targety = 94;
        } else if (offsetmove % 900 < 270) {
            var targetx = 34;
            var targety = 94 - ((offsetmove - 30) % 60);
        } else if (offsetmove % 900 < 300) {
            var targetx = 34 + (offsetmove % 30);
            var targety = 34 + (offsetmove % 30);
        } else if (offsetmove % 900 < 360) {
            var targetx = 64 + (offsetmove % 60);
            var targety = 64 - (offsetmove % 60);
        } else if (offsetmove % 900 < 480) {
            var targetx = 124;
            var targety = 4 + (offsetmove % 120);
        } else if (offsetmove % 900 < 600) {
            var targetx = 124 - (offsetmove % 120);
            var targety = 124;
        } else if (offsetmove % 900 < 720) {
            var targetx = 4;
            var targety = 124 - (offsetmove % 120);
        } else if (offsetmove % 900 < 840) {
            var targetx = 4 + (offsetmove % 120);
            var targety = 4;
        } else {
            var targetx = 124 - (offsetmove % 60);
            var targety = 4 + (offsetmove % 60);
        }

        if (offsetint % 4 == 1) {
            var temp = targetx;
            var targetx = 127 - targety;
            var targety = temp;
        } else if (offsetint % 4 == 2) {
            var targetx = 127 - targetx;
            var targety = 127 - targety;
        } else if (offsetint % 4 == 3) {
            var temp = targetx;
            var targetx = targety;
            var targety = 127 - temp;
        }

        if ((offsetint >> 3) % 2) {
            var targetx = 127 - targetx;
        }

        var bestmove = 0;

        for (var j = 0; j < 9; j++) {
            safe[j] -= Math.abs(topx + disp[j][0] - targetx) + Math.abs(topy + disp[j][1] - targety);

            if (safe[j] > safe[bestmove]) {
                bestmove = j;
            }
        }

        var strpos = tochar(x + disp[bestmove][0]) + tochar(y + disp[bestmove][1]);

        if (bestmove > 6) {
            setMsg(otherpos + strpos + movestat + offset);
            if (formpos == "T"){return 13 - bestmove;}
            return bestmove - 4;
        }

        setMsg(formpos + strpos + movestat + offset);
        return bestmove;
    }
}

This bot forms a pair with Sp3000's bot.

The basic idea is that two bots, positioned adjacent to each other, help cover each other's weaknesses, so that neither bot has an exposed side. This helps protect from threats and limit the escape options of the target.

At the beginning of the game, they navigate towards each other and form a pair. This pair then moves as a single unit, with one bot leading the other. The two bots have almost identical code, allowing them to trade positions and roles when necessary.

When idle, the bots move around the board searching for enemies. Once they spot an enemy, they carefully maneuver themselves into the correct positions to attack. A very neat feature is the formation's ability to move straight horizontally, achieved by having the bots alternate places.

\$\endgroup\$
4
  • 9
    \$\begingroup\$ Oh dear! As soon as they form a pair it's an automatic loss for red. \$\endgroup\$
    – Minos
    Commented Apr 4, 2015 at 9:07
  • \$\begingroup\$ @Minos Pretty much, yes :) \$\endgroup\$
    – user36219
    Commented Apr 4, 2015 at 12:15
  • \$\begingroup\$ I thought of writing a similar bot for red but didn't get around to it before, now I have to come up with a strategy that can beat the duo. \$\endgroup\$
    – Etheryte
    Commented Apr 4, 2015 at 20:43
  • \$\begingroup\$ For the record, I've seen that there's a place where var is not used in this post and Sp3000's. They assign j to 0 right away and it doesn't interfere with the controller at all, so it is luckily not a problem in this case. \$\endgroup\$ Commented Apr 6, 2015 at 23:37
49
+500
\$\begingroup\$

Blue Team - SphiNotPi3000

// Char 0: top or bottom ("T" or "B")
// Char 1, 2: x/y coords
// Char 3, move polarity
// Char 4: offset (as codepoint - 128)

var myself = 21487;
var twin = 2867;
var formpos = "B";

var tochar = String.fromCharCode;
var movestat = (move % 2).toString();

var inbox = getMsg(twin);

// Spoofing the message of a deceased partner
if (inbox == "X"){
    inbox = "B" + tochar(x) + tochar(y+1) + ((move + 1) % 2).toString() + tochar(0);
}

var selfsafe = [9,10,10,10,10,10,10];

// Remove useless edge moves
if (x == 0){
    selfsafe[4] = 0;
    selfsafe[5] = 0;
}
if (x == 127){
    selfsafe[3] = 0;
    selfsafe[6] = 0;
}
if (y == 0){
    selfsafe[2] = 0;
    selfsafe[3] = 0;
    selfsafe[4] = 0;
}
if (y == 127){
    selfsafe[1] = 0;
    selfsafe[6] = 0;
    selfsafe[5] = 0;
}

var selfdisp = [[0,0],[0,1],[0,-1],[1,-1],[-1,-1],[-1,1],[1,1]];

if (inbox == "") {
    // First move, pick anywhere safe

    for (j = 0; j < 7; j++) {
        for (var i = 0; i < eNear.length; i++){
            var enemy = eNear[i];
            var dx = enemy.x - x - selfdisp[j][0];
            var dy = enemy.y - y - selfdisp[j][1];

            if (dx * dx == 1 && dy >= -1 && dy <= 1) {
                selfsafe[j] = 0;
            }
        }

        if (selfsafe[j]) {
            var strpos = tochar(x + selfdisp[j][0]) + tochar(y + selfdisp[j][0]);
            var offset = tochar(Math.floor(Math.random() * 256));
            setMsg(formpos + strpos + movestat + offset);
            return j;
        }
    }

} else {
    var twinformpos = inbox.charAt(0);
    var twinx = inbox.charAt(1).charCodeAt();
    var twiny = inbox.charAt(2).charCodeAt();
    var twinmovestat = inbox.charAt(3);
    var offset = inbox.charAt(4);

    formpos = twinformpos == "T" ? "B" : "T";
    var targetx = twinx;
    var targety = formpos == "T" ? (twiny - 1) : (twiny + 1);

    // If true, then this bot is either the second one to move or is not in position.  Move into position.
    if (twinmovestat == movestat || x != targetx || y != targety) {
        var bestmove = 0;

        for (var j = 0; j < 7; j++) {
            for (var i = 0; i < eNear.length; i++){
                var enemy = eNear[i];
                var dx = enemy.x - x - selfdisp[j][0];
                var dy = enemy.y - y - selfdisp[j][1];

                if (dx * dx == 1 && dy >= -1 && dy <= 1) {
                    selfsafe[j] = 0;
                }
                if (dx == 0 && dy == 0){
                    selfsafe[j] *= 2;
                }
            }

            selfsafe[j] -= Math.abs(x + selfdisp[j][0] - targetx) + Math.abs(y + selfdisp[j][1] - targety);

            if (selfsafe[j] > selfsafe[bestmove]) {
                bestmove = j;
            }
        }

        var strpos = tochar(x + selfdisp[bestmove][0]) + tochar(y + selfdisp[bestmove][1]);
        setMsg(formpos + strpos + movestat + offset);
        return bestmove;

    } else {
        // In formation, and is the leader this turn

        var topy = formpos == "T" ? y : (y - 1);
        var topx = x;
        var safe = [1,1,1,1,1,1,1,1,1];
        var disp = [[0,0],[0,1],[0,-1],[1,-1],[-1,-1],[-1,1],[1,1],[1,0],[-1,0]];
        var otherpos = formpos == "T" ? "B" : "T";

        // Avoid dangerous squares and always kill if safe to do so
        for (var j = 0; j < 9; j++){
            var ntopx = topx + disp[j][0];
            var ntopy = topy + disp[j][1];

            if (ntopx < 0 || ntopx > 127 || ntopy < 0 || ntopy > 126){
                safe[j] = 0;
                continue;
            }

            for (var i = 0; i < eNear.length; i++){
                var enemy = eNear[i];
                var dx = enemy.x - ntopx;
                var dy = enemy.y - ntopy;

                if(dx * dx == 1 && dy >= -1 && dy <= 2){
                    safe[j] = 0;
                    continue;
                }

                if(dx == 0 && dy >= 0 && dy <= 1){
                    // Kill!
                    var strpos = tochar(x + disp[j][0]) + tochar(y + disp[j][1]);

                    if (j > 6) {
                        setMsg(otherpos + strpos + movestat + offset);
                        if (formpos == "T"){return 13 - j;}
                        return j - 4;
                    }

                    setMsg(formpos + strpos + movestat + offset);
                    return j;
                }
            }
        }

        var pref = [];

        for (var i = 0; i < eNear.length; i++){
            var enemy = eNear[i];
            var dy = enemy.y - topy;
            var dx = enemy.x - topx;

            if (dy < 0 && dx == 0){ pref=[2,4,3,8,7,1,5,6,0]; }
            if (dy > 0 && dx == 0){ pref=[1,5,6,7,8,2,4,3,0]; }
            if (dy == 0 && dx > 0){ pref=[7,6,3,1,2,5,4,8,0]; }
            if (dy == 0 && dx < 0){ pref=[8,5,4,1,2,6,3,7,0]; }
            if (dy < 0 && dx < 0){ pref=[4,8,5,1,0,2,6,7,3]; }
            if (dy > 0 && dx < 0){ pref=[5,8,4,2,0,1,3,7,6]; }
            if (dy < 0 && dx > 0){ pref=[3,7,6,1,0,2,5,8,4]; }
            if (dy > 0 && dx > 0){ pref=[6,7,3,2,0,1,4,8,5]; }

            for (var k = 0; k < pref.length; k++)
            {
                if (safe[pref[k]]){
                    var strpos = tochar(x + disp[pref[k]][0]) + tochar(y + disp[pref[k]][1]);

                    if (pref[k] > 6) {
                        setMsg(otherpos + strpos + movestat + offset);
                        if(formpos == "T"){return 13 - pref[k];}
                        return pref[k] - 4;
                    }

                    setMsg(formpos + strpos + movestat + offset);
                    return pref[k];
                }
            }
        }

        var offsetint = offset.charCodeAt();
        var offsetmove = move - 128 + offsetint;

        if (offsetmove % 900 < 30) {
            var targetx = 64 - (offsetmove % 30);
            var targety = 64 - (offsetmove % 30);
        } else if (offsetmove % 900 < 90) {
            var targetx = 34 + ((offsetmove - 30) % 60);
            var targety = 34;
        } else if (offsetmove % 900 < 150) {
            var targetx = 94;
            var targety = 34 + ((offsetmove - 30) % 60);
        } else if (offsetmove % 900 < 210) {
            var targetx = 94 - ((offsetmove - 30) % 60);
            var targety = 94;
        } else if (offsetmove % 900 < 270) {
            var targetx = 34;
            var targety = 94 - ((offsetmove - 30) % 60);
        } else if (offsetmove % 900 < 300) {
            var targetx = 34 + (offsetmove % 30);
            var targety = 34 + (offsetmove % 30);
        } else if (offsetmove % 900 < 360) {
            var targetx = 64 + (offsetmove % 60);
            var targety = 64 - (offsetmove % 60);
        } else if (offsetmove % 900 < 480) {
            var targetx = 124;
            var targety = 4 + (offsetmove % 120);
        } else if (offsetmove % 900 < 600) {
            var targetx = 124 - (offsetmove % 120);
            var targety = 124;
        } else if (offsetmove % 900 < 720) {
            var targetx = 4;
            var targety = 124 - (offsetmove % 120);
        } else if (offsetmove % 900 < 840) {
            var targetx = 4 + (offsetmove % 120);
            var targety = 4;
        } else {
            var targetx = 124 - (offsetmove % 60);
            var targety = 4 + (offsetmove % 60);
        }
        
        if (offsetint % 4 == 1) {
            var temp = targetx;
            var targetx = 127 - targety;
            var targety = temp;
        } else if (offsetint % 4 == 2) {
            var targetx = 127 - targetx;
            var targety = 127 - targety;
        } else if (offsetint % 4 == 3) {
            var temp = targetx;
            var targetx = targety;
            var targety = 127 - temp;
        }

        if ((offsetint >> 3) % 2) {
            var targetx = 127 - targetx;
        }

        var bestmove = 0;

        for (var j = 0; j < 9; j++) {
            safe[j] -= Math.abs(topx + disp[j][0] - targetx) + Math.abs(topy + disp[j][1] - targety);

            if (safe[j] > safe[bestmove]) {
                bestmove = j;
            }
        }

        var strpos = tochar(x + disp[bestmove][0]) + tochar(y + disp[bestmove][1]);

        if (bestmove > 6) {
            setMsg(otherpos + strpos + movestat + offset);
            if (formpos == "T"){return 13 - bestmove;}
            return bestmove - 4;
        }

        setMsg(formpos + strpos + movestat + offset);
        return bestmove;
    }
}

This bot forms a pair with PhiNotPi's bot. See Phi's post for a brief explanation of our strategy.

\$\endgroup\$
1
  • \$\begingroup\$ Ouch! Good one.. \$\endgroup\$ Commented Apr 4, 2015 at 3:13
21
\$\begingroup\$

Red Team - SeekerBot

var myself = 38926;
var messages = getMsg(myself).split(';');
var minimalDistanceToFriend = 2;
var chosenMove = null;
var newDistanceToFriend = null;
var minimalVerticalDistanceToEnemy = null, minimalHorizontalDistanceToEnemy = null;
var closestFriend = null;
var closestEnemy = null;
var possibleVictims = [];
var possibleMoves = [
    {newX: x, newY: y},
    {newX: x + 1, newY: y},
    {newX: x - 1, newY: y},
    {newX: x + 1, newY: y - 1},
    {newX: x - 1, newY: y - 1},
    {newX: x - 1, newY: y + 1},
    {newX: x + 1, newY: y + 1}
];

var calculateDistance = function(x1, y1, x2, y2) {
    return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
};

var iAmInDanger = function(meX, meY, himX, himY) {
    return (Math.abs(meY - himY) === 1 && Math.abs(meX - himX) <= 1);
};

var iCanKillHim = function(meX, meY, himX, himY) {
    return (Math.abs(meX - himX) === 1 && Math.abs(meY - himY) <= 1);
};

var setMessage = function() {
    messages[0] = ("000" + x).substr(-3, 3);
    messages[1] = ("000" + y).substr(-3, 3);
    setMsg(messages.join(';'));
}

for (i = 0; i < possibleMoves.length; i++) {
    if (possibleMoves[i].newX < 0 || possibleMoves[i].newY < 0 || possibleMoves[i].newX > 127 || possibleMoves[i].newY > 127) {
        possibleMoves[i] = null;
    }
}

for (var i = 0; i < eNear.length; i++) {
    if (closestEnemy === null || calculateDistance(x, y, closestEnemy.x, closestEnemy.y) > calculateDistance(x, y, eNear[i].x, eNear[i].y)) {
        closestEnemy = eNear[i];
    }
    if (Math.abs(x - eNear[i].x) <= 2 && Math.abs(y - eNear[i].y) <= 2) {
        possibleVictims.push(eNear[i]);
    }
}

for (i = 0; i < tNear.length; i++) {
    if (closestFriend === null || calculateDistance(x, y, closestFriend.x, closestFriend.y) > calculateDistance(x, y, tNear[i].x, tNear[i].y)) {
        closestFriend = tNear[i];
    }
}    

for (i = 0; i < possibleMoves.length; i++) {
    for (var j = 0; j < possibleVictims.length; j++) {
        if (possibleMoves[i] !== null && iAmInDanger(possibleMoves[i].newX, possibleMoves[i].newY, possibleVictims[j].x, possibleVictims[j].y)) {
            possibleMoves[i] = null;
        }
    }
}

for (i = 0; i < possibleMoves.length; i++) {
    for (j = 0; j < possibleVictims.length; j++) {
        if (possibleMoves[i] !== null && possibleMoves[i].newX === possibleVictims[j].x && possibleMoves[i].newY === possibleVictims[j].y) {
            messages[2] = 0;
            setMessage();
            return i;
        }
    }
}

if (possibleVictims.length > 0) {
    if (iAmInDanger(x, y, possibleVictims[0].x, possibleVictims[0].y)) {
        if (closestFriend !== null) {
            for (i = 0; i < possibleMoves.length; i++) {
                if (possibleMoves[i] !== null) {
                    var distance = calculateDistance(possibleMoves[i].newX, possibleMoves[i].newY, closestFriend.x, closestFriend.y);
                    if (newDistanceToFriend === null || (distance < newDistanceToFriend && distance >= minimalDistanceToFriend)) {
                        newDistanceToFriend = distance;
                        chosenMove = i;
                    }
                }
            }
            messages[2] = 0;
            setMessage();
            return chosenMove;
        }
        else {
            var aggressiveMoves = [];
            var randomMoves = [];

            for (i = 0; i < possibleMoves.length; i++) {
                if (possibleMoves[i] !== null) {
                    if (iCanKillHim(possibleMoves[i].newX, possibleMoves[i].newY, possibleVictims[0].x, possibleVictims[0].y)) {
                        aggressiveMoves.push(i);
                    }
                    randomMoves.push(i);
                }
            }
            var approachCount = messages[2] || 0;
            if (approachCount < 5 && aggressiveMoves.length > 0) {
                messages[2] = approachCount + 1;
                chosenMove = aggressiveMoves[Math.floor(Math.random() * aggressiveMoves.length)];
                setMessage();
                return chosenMove;
            } 
            else {
                chosenMove = randomMoves[Math.floor(Math.random() * randomMoves.length)];
                setMessage();
                return chosenMove;
            }
        }
    }
}

if (closestEnemy != null) {
    for (i = 1; i < possibleMoves.length; i++) {
        if (possibleMoves[i] !== null) {
            var verticalDistance = Math.abs(possibleMoves[i].newY - closestEnemy.y);
            var horizontalDistance = Math.abs(possibleMoves[i].newX - closestEnemy.x);
            if (minimalVerticalDistanceToEnemy === null || verticalDistance <= minimalVerticalDistanceToEnemy) {
                if (minimalVerticalDistanceToEnemy !== null && verticalDistance === minimalVerticalDistanceToEnemy) {
                    if (minimalHorizontalDistanceToEnemy === null || horizontalDistance <= minimalHorizontalDistanceToEnemy) {
                        minimalHorizontalDistanceToEnemy = horizontalDistance;
                        chosenMove = i;
                    }
                }
                else {
                    minimalVerticalDistanceToEnemy = verticalDistance;
                    minimalHorizontalDistanceToEnemy = horizontalDistance;
                    chosenMove = i;
                }                                        
            }
        }            
    }
    messages[2] = 0;
    setMessage();
    return chosenMove;
}

var seekStatus = messages[3] || 0;
var seekCount = messages[4] || 0;
seekStatus = parseInt(seekStatus, 10);
seekCount = parseInt(seekCount, 10);

switch (seekStatus) {
    case 0:
        if (x < 16) {
            seekCount = 0;
            if (y > 111) {
                seekStatus = 4;
            }
            else {
                seekStatus = 1;
            }                
        }
        else {
            chosenMove = 2;
        }
        break;
    case 1:
        seekCount++;
        if (y > 111 || seekCount > 31) {
            seekStatus = 2;
        }            
        else {
            if (seekCount % 2 === 0) {
                chosenMove = 5;
            }
            else {
                chosenMove = 6;
            }
        }
        break;
    case 2:
        if (x > 111) {
            seekCount = 0;
            if (y > 111) {
                seekStatus = 4;
            }
            else {
                seekStatus = 3;
            }                   
        }
        else {
            chosenMove = 1;
        }
        break;
    case 3:
        seekCount++;
        if (y > 111 || seekCount > 31) {
            seekStatus = 0;
        }
        else {
            if (seekCount % 2 === 0) {
                chosenMove = 5;
            }
            else {
                chosenMove = 6;
            }
        }
        break;
    case 4:
        seekCount++;
        if (y < 16) {
            if (x > 63) {
                seekStatus = 0;
            }
            else {
                seekStatus = 2;
            }
        }
        else {
            if (seekCount % 2 === 0) {
                chosenMove = 3;
            }
            else {
                chosenMove = 4;
            }
        }
        break;
}

messages[2] = 0;
messages[3] = seekStatus;
messages[4] = seekCount;    

setMessage();
return chosenMove;

The highest priority of SeekerBot is survival. Therefore, it will only consider moves which will not put it into jeopardy of being killed in the next turn (as long as such moves exist).

When no opponents are in view, it will move in a pattern over the battlefield, which will ensure that most of the ground will regularly be in viewing distance.

If SeekerBot spots an enemy, it will move towards it. If it can kill an enemy, it will do so as long as the move is save.

If it cannot kill an enemy but the enemy is in a position to kill it on its next turn, SeekerBot will try to lure the enemy towards a friend (if one is visible). If no team member is in view, it will try to move into a position, where it can kill the enemy in the next turn. If this does not work 5 times in a row, it will switch tactics and start moving in a random pattern, possibly closing in on the enemy again in the next round.

For what it's worth, it will use the first 7 characters of the message to shout its own position in the format "x;y" (where x and y are zero padded).

Its certainly not the cleanest code, but it seems to do what I expected of it.

\$\endgroup\$
4
  • 2
    \$\begingroup\$ You are killing! \$\endgroup\$ Commented Apr 3, 2015 at 18:39
  • 2
    \$\begingroup\$ You've brought the red team up to winning every time I've run it. Will be interesting to see what the two new blue bots do once they gang up. \$\endgroup\$
    – Adam Davis
    Commented Apr 3, 2015 at 18:50
  • \$\begingroup\$ It is very fun to watch this slaughter the blues :) \$\endgroup\$ Commented Apr 3, 2015 at 23:13
  • \$\begingroup\$ @AdamDavis Those two blue bots coordinating is insane; every game I've watched so far they have thrashed! \$\endgroup\$
    – user36219
    Commented Apr 4, 2015 at 12:14
12
\$\begingroup\$

Red Team - Groomba

// v009
// I exist, therefore I am identifiable and have motivation
var myself = 1686;
var motive = [ 4,4, 4,-1, 4,3, 3,-1, 
               3,3, 3,1, 1,1, 6,1,
               6,6, 6,-2, 6,5, 5,-2,
               5,5, 5,2, 2,2, 4,2]; 
var killzone = [4,2,5, 3,1,6];

// Default move is to not move.  Then we consider each task in lowest
// to highest priority.  Each task only modifies the move if it needs to.
var move = 0;
var vector = 0;
var step = 0;

// Restore internal state from message
var selfMessage;
selfMessage = getMsg(myself);
if(selfMessage === undefined || selfMessage.length > 2) // first run or bigger than 99, let's make some defaults!
{
   // vector, step - let the default above stand
}
else
{
   vector = Math.floor(parseInt(selfMessage)/2) % 16;
   step = parseInt(selfMessage) % 2;
}

// 1) Move according to motivation
move = motive[vector*2 + step];
step = (step + 1) % 2;

if(move == -1)
{
   move = Math.floor(Math.random() * 2) + 3;
}

if(move == -2)
{
   move = Math.floor(Math.random() * 2) + 5;
}

// 2) When interacting with a wall, rebound but alter the angle 
//    slightly so as to prevent trivial counterattack strategies
// If we are close to a wall and headed towards that wall, randomly
// choose another vector to follow.
if((y < 8 && (vector > 14 || vector < 6)) ||
   (y > 120 && (vector > 6 && vector < 14)) ||
   (x < 8 && (vector > 10 || vector < 2)) ||
   (x > 120 && (vector > 2 && vector < 10)))
{
   vector = Math.floor(Math.random() * 16);
}
    
// When an enemy is within view, move beside them

if(eNear.length > 0) // I only look at the first enemy in the array.
{
    enemy = eNear[0];
    if(enemy.x == x) // Don't want to be directly on top or below
    {
       if(enemy.y > y) // If they are below move angular down
       {
           move = (x > 63) ? 5 : 6;
       }
       else
       {
           move = (x > 63) ? 4 : 3;
       }
       move = 1;
    }
    else if(enemy.y > y)
    {
       if(enemy.x > x)
       {
           move = 6;
       }
       else
       {
           move = 5;
       }
       vector = 10;
    }
    else if(enemy.y != y)
    {
       if(enemy.x > x)
       {
           move = 3;
       }
       else
       {
           move = 4;
       }
        vector = 2;
    }
    else
    {
        if(enemy.x > x)
        {
            move = 1;
            vector = 6
        }
        else
        {
            move = 2;
            vector = 14;
        }
    }
}

// 3) When an enemy is one space away, act or react.
//    A) If it can be consumed, consume
//    B) If it can consume us next turn, evade
//    C) If we can reposition ourselves to consume next turn, reposition

var enemy;
var difx;
var dify;

// Evade
for(var i=0; i<eNear.length; i++) {
    enemy = eNear[i];
    if(enemy.x == x && enemy.y == y + 1)
    {
       if(x>63)
       {
           move = 5;
       }
       else
       {
           move = 6;
       }
    }
    if(enemy.x == x && enemy.y == y - 1)
    {
       if(x>63)
       {
           move = 4;
       }
       else
       {
           move = 3;
       }
    }
}

// Kill
for(var i=0; i<eNear.length; i++) {
    enemy = eNear[i];
    difx = enemy.x - x + 1;
    dify = enemy.y - y + 1;
    if((difx == 0 || difx == 2) && (dify > -1 && dify < 3))
    {
       move = killzone[Math.floor(difx/2) * 3 + dify];
    }
}

// 4) Encode the current surroundings and internal state
var value = vector*2+step
var message = value.toString();
setMsg(message);

// Return move
return move;

Notes in comments.

\$\endgroup\$
1
  • 3
    \$\begingroup\$ Don't use self. That variable is reserved to point to window.self. Use I (capital i) instead. Or me. Or even myself. \$\endgroup\$ Commented Apr 2, 2015 at 17:47
11
\$\begingroup\$

Red Team - Lazy Slayer

var moves={
    '-1':{'-1':4,'0':0,'1':3},
    '0':{'-1':2,'0':0,'1':1},
    '1':{'-1':5,'0':0,'1':6}
},$id=14732,to,enemies='';

for(var k in eNear)
{
    enemies+=String.fromCharCode(eNear[k].x+32)+String.fromCharCode(eNear[k].y+32);
}

enemies=enemies.replace('"','\\"');

for(var k in eNear)
{
    to=undefined;
    switch( eNear[k].x - x )
    {
        case -1:
        case 1:
            to=moves[eNear[k].y - y][eNear[k].x - x];
            break;
        case 0:
            to=moves[-(eNear[k].y - y)][0];
            break;
    }
    if(to!==undefined)
    {
        setMsg('"a":1,"o":['+x+','+y+'],"m":'+(to||0)+',"e":"'+enemies+'"');
        return to;
    }
}

var msg;

for(var k in tNear)
{
    if(msg = getMsg(tNear[k].id))
    {
        try
        {
            var m=JSON.parse('{'+msg+'}');
            if(m && m[$id])
            {
                if(m[$id].a === 1)
                {
                    if(!m[$id].x || !m[$id].y)
                    {
                        setMsg('"a":1,"o":['+x+','+y+'],"m":'+m[$id].m+',"id":'+m[$id].id+'}');
                        return m[$id].m;
                    }
                    else
                    {
                        setMsg('"a":1,"o":['+x+','+y+'],"m":{"x":'+m[$id].x+',"y":'+m[$id].y+'},"id":'+m[$id].id+',"e":"'+enemies+'"');
                        return moves[m[$id].x][m[$id].y];
                    }
                }
                else if(m[$id].a === 0)
                {
                    setMsg('"a":0,"o":['+x+','+y+'],"m":0,"id":'+m[$id].id+',"e":"'+enemies+'"');
                    return moves[m[$id].x||0][m[$id].y||0];
                }
            }
        }
        catch(e){}
    }
}

setMsg('"a":0,"o":['+x+','+y+'],"m":0,"e":"'+enemies+'"');
return 0;

This is the most basic I could get it. This no longer is 100% basic.

It only moves IF REQUIRED.

If a user sends a message with 2 numbers between -1 and 1 (e.g: '1,0'), separated with a comma, it will move there. It totally trusts it's teammates.

This now communicates over JSON. It has a very basic structure:

  • getMsg:
    • a: determins the action:
      • 0: stop
      • 1: move
    • m: The movement to send
      • {x:n,y:n}: Object with x and y, between -1 and 1
      • raw value to be returned with the number represented in the grid.
  • setMsg:
    • a: indicates if it moved or if it stopped;
    • o: an array with my old position;
    • m: the raw movement returned by the code;
    • id: if an order was respected, this will have the guilty's ID;
    • e: list with all the enemy positions. Each position is biased by 32, to avoid non-printable chars. Use string.charCodeAt(i)-32 to get the enemy position. This will be a string with even length. Each enemy will be 2 chars.

An example of a message to control it:

"14732":{"a":1,"m":3}

Which will send:

"a":1,"o":[x,y],"m":3,"id":id,"e":""

He is also a little selfish and won't help you and now he is helpful as a stationary beacon.

If this message is wrong (the format isn't right), try adding "} instead of }.


This was edited after the 6-hour limit, and after it was extended to 8.

It is no longer broken and will remain as the final version.

\$\endgroup\$
16
  • 2
    \$\begingroup\$ As someone on the red team, I think one crucial thing we should include is always set a message with your current state and surrounding in some agreed-upon form of an object (this must include what move this was set on, otherwise bots won't know which state is outdated). This will allow other, later bots to be aware of more than their own surroundings, possibly the whole board, and make better decisions. \$\endgroup\$
    – Etheryte
    Commented Apr 2, 2015 at 11:05
  • \$\begingroup\$ @Nit I agree with you, and that's why I made this choice to use JSON to communicate. I have 5 hours and 22m to make changes. any more ideas? \$\endgroup\$ Commented Apr 2, 2015 at 11:11
  • 1
    \$\begingroup\$ I'm thinking the first few need to be scanners. We only need four to sweep side to side to cove the entire board (32 effective scanning width covers 1/4 of the board). Move side to side, setmsg nearest N bad guys using a binary or compressed string, and either avoid bad guys, or seek and kill bad guys using a reasonable strategy to avoid being killed (never let them get on your corner, nor above or below you?). \$\endgroup\$
    – Adam Davis
    Commented Apr 2, 2015 at 11:33
  • 1
    \$\begingroup\$ @IsmaelMiguel Nope. I'm hoping to submit one soon as well, but not knowing Javascript is slowing me down. \$\endgroup\$
    – Adam Davis
    Commented Apr 2, 2015 at 16:34
  • 3
    \$\begingroup\$ If it isn't broken, remove the old obsolete comments to reduce clutter. \$\endgroup\$
    – Etheryte
    Commented Apr 2, 2015 at 18:16
10
\$\begingroup\$

Red Team - The Coward

var bounds = 128;
var movements = [[0,0], [-1,-1],[1,-1],[-1,0],[1,0],[-1,1],[1,1]];

var distanceTo = function(x, y, pixel)
{
    var a = x - pixel.x;
    var b = y - pixel.y;
    return Math.sqrt( a*a + b*b );
}

var isDiagonallyAdjacent = function(x, y, pixel)
{
    return (Math.abs(pixel.x - x) == 1 && Math.abs(pixel.y - y) == 1);
}

var canAttackMe = function(x, y, pixel)
{
    if(x == pixel.x && Math.abs(pixel.y - y) == 1)
    {
        return true;
    }
    else
    {
        return isDiagonallyAdjacent(x, y, pixel);
    }
}

var canIAttack = function(x, y, pixel)
{
    if(y == pixel.y && Math.abs(pixel.x - x) == 1)
    {
        return true;
    }
    else
    {
        return isDiagonallyAdjacent(x, y, pixel);
    }
}

var isPositionSafe = function(x2, y2, enemies)
{
    var safe = true;
    for(var i in enemies)
    {
        if(canAttackMe(x2, y2, enemies[i]))
        {
            safe = false;
            break;
        }
    }
    return safe;
}

var moveTo = function(x, y, x2, y2)
{
    if(x2 < x)
    {
        if(y2 < y) return 4;
        else if(y2 > y) return 5;
        else return 2;
    }
    else if(x2 > x)
    {
        if(y2 < y) return 3;
        else if(y2 > y) return 6;
        else return 1;
    }
    else
    {
        if(y2 < y)
        {
            if(x2 < bounds)
            {
                return 3;
            }
            return 4;
        }
        else if(y2 > y)
        {
            if(x2 >= 0)
            {
                return 5;
            }
            return 6;
        }
    }
    return 0;
}

var getMovement = function(i)
{
    var m = [[0, 0], [1, 0], [-1, 0], [1, -1], [-1, -1], [-1, 1], [1, 1]];
    return m[i];
}

if(eNear.length == 0)
{
    // Move at random
    //return Math.floor((Math.random() * 6) + 1);
    return 0;
}
else 
{
    var safePositions = [];
    var isSafePosition = function(x2, y2)
    {
        for(var i in safePositions)
        {
            if(safePositions[i][0]==x2 && safePositions[i][0]==y2)
            {
                return true;
            }
        }
        return false;
    }
    
    for(var i in movements)
    {
        var x2 = x + movements[i][0];
        var y2 = y + movements[i][1];
        if(x2 >= 0 && x2 < bounds && y2 >= 0 && y2 < bounds
            && isPositionSafe(x2, y2, eNear))
        {
            safePositions.push([x + movements[i][0], y + movements[i][1]]);
        }
    }
    
    var dangerousPixels = [];
    var attackablePixels = [];
    var kamikazePixels = [];
    
    for(var ei in eNear)
    {
        var e = eNear[ei];
        var attackable = canIAttack(x, y, e);
        var dangerous = canAttackMe(x, y, e);
        if( attackable )
        {
            if(isSafePosition(e.x, e.y))
            {
                attackablePixels.push(e);
            }
            else
            {
                kamikazePixels.push(e);
            }
        }
        else if(dangerous)
        {
            dangerousPixels.push(e);
        }
    }
    if(attackablePixels.length == eNear.length)
    {
        return moveTo(attackablePixels[0].x, attackablePixels[0].y);
    }
    if(attackablePixels.length > 0 && tNear.length >= eNear.length)
    {
        // Attack only if we have greater numbers
        // Attack one of them at random
        var i = Math.floor(Math.random() * attackablePixels.length);
        return moveTo(x, y, attackablePixels[i].x, attackablePixels[i].y);
    }
    else if(dangerousPixels.length > 0 && safePositions.length > 0)
    {
        // Flee
        var i = Math.floor(Math.random() * safePositions.length);
        return moveTo(x, y, safePositions[i][0], safePositions[i][1]);
        
    }
    else if(dangerousPixels.length > 0 && safePositions.length == 0 && kamikazePixels.length > 0)
    {
        var i = Math.floor(Math.random() * kamikazePixels.length);
        return moveTo(x, y, kamikazePixels[i].x, kamikazePixels[i].y);
    }
    else 
    {
        var nearest = null;
        var nearestDist = Infinity;
        for(var ei in eNear)
        {
            var e = eNear[ei];
            var d = distanceTo(x, y, e);
            if(nearest === null || d < nearestDist)
            {
                nearestDist = d;
                nearest = e;
            }
        }
        
        if(tNear.length >= eNear.length)
        {
            // Attack the nearest
            return moveTo(x, y, nearest.x, nearest.y);
        }
        else
        {
            // Get Away from the nearest
            var n = moveTo(x, y, nearest.x, nearest.y);
            var m = getMovement(n);
            var x2 = x-m[0];
            var y2 = y-m[1];
            if(x2 < 0 || x2 >= bounds) x2 = x + m[0];
            if(y2 < 0 || y2 >= bounds) y2 = y + m[1];
            return moveTo(x, y, x2, y2);
        }
    }
}

This bot stays still to avoid being detected as much as possible. When one or more enemies are in sight, several things can happen:

  • If there are more enemies than the bot and his allies in sight, he tries to get away from the nearest enemy.
  • If there are more friends in sight than enemies, the numeric superiority gives the bot courage and tries to go forth and attack the enemy.
  • If he can kill an enemy next to it, he will try to, regardless of how many friendly and enemy bots there are, always trying to end up in a cell that can't be attacked by another bot.
  • If all the enemies are protected by other enemies, tries to flee.
  • If he can not go anywhere because all positions can be attacked, enters kamikaze mode and tries to at least take someone with him to the grave.

Doesn't communicate with anyone, in case someone could hear him and go after him.

It may not be the most useful bot to the team, but it was fun watching him trying to get away from everyone.

\$\endgroup\$
2
  • \$\begingroup\$ Okay, I love your bot. Mine is kinda the opposite (it seeks enemies and then runs to the center), so they have a fantastic interaction where mine might try to tease yours out of the corner and drag it to the middle, but then if yours sees somebody it gets scared and runs back to the corner, while mine chases it. \$\endgroup\$ Commented Apr 4, 2015 at 21:37
  • \$\begingroup\$ Thanks! I find it funny seeing him interact with the others. But I found a couple bugs regarding the boundaries and how it bounces, but too late to fix them... So it's not as good as I'd like it to be. \$\endgroup\$
    – ror3d
    Commented Apr 4, 2015 at 21:49
9
\$\begingroup\$

Blue team - Eagle

var move_valid = function(x, y, move){
    var move_x = {0:0, 1:0, 2:0, 3:1, 4:-1, 5:-1, 6:1};
    var move_y = {0:0, 1:1, 2:-1, 3:-1, 4:-1, 5:1, 6:1};
    var xx = x + move_x[move];
    var yy = y + move_y[move];
    return (1 <= xx && xx <= 125 && 1 <= yy && yy <= 125);
}
var sign = function(x){
    if (x === 0) return 0;
    else if (x > 0) return 1;
    else return -1;
}

if (eNear.length === 0) {
    if (getMsg(29577).length > 0) {
        var last_move = parseInt(getMsg(29577).charAt(0))
        if (last_move !== 0 && 
            move_valid(x, y, last_move) &&
            Math.random() > 0.03) return last_move;
    }

    var moves = [1, 2, 3, 4, 5, 6];
    var valid_moves = [];
    for (var move of moves){if (move_valid(x, y, move)) valid_moves.push(move);}
    if (valid_moves.length === 0) valid_moves.push(0);
    var move = moves[Math.floor(Math.random()*moves.length)];
    setMsg(move.toString());
    return move;
} else {
    var enemy = eNear[0];
    var dist = Math.max(Math.abs(x- enemy.x), Math.abs(y - enemy.y))
    var dir_x = sign(enemy.x - x);
    var dir_y = sign(enemy.y - y);
    var dir_to_move = {1: {1: 6, 0: -1, "-1": 3}, 0: {1: 1, 0: 1, "-1": 2}, "-1": {1: 5, 0: -1, "-1": 4}};
    var move = dir_to_move[dir_x][dir_y];
    var fight_count = 0;
    if (getMsg(29577).length > 1) {
        fight_count = parseInt(getMsg(29577).substring(1));
    }
    fight_count += 1;
    if (fight_count > 100){
        if (fight_count > 110) fight_count = 0;
        move = dir_to_move[-dir_x][dir_x !== 0 ? -dir_y : (Math.random() > 0.5 ? 1 : -1)];
        setMsg(move.toString() + fight_count.toString());
        return move;
    } else {
        if (dist > 2) {
            // Move towards enemy
            if (move === -1) move = dir_to_move[dir_x][Math.random() > 0.5 ? 1 : -1]
            setMsg(move.toString() + fight_count.toString());
            return move;
        } else if (dist === 2) {
            if (Math.abs(x - enemy.x) < 2) {
                // go one down if === 0
                // go diagonal, if ===1
                // move is already correct  
            } else if (Math.abs(y - enemy.y) === 2) {
                // dist_x == dist_y
                move = dir_to_move[0][dir_y];
            } else if (Math.abs(y - enemy.y) === 1) {
                move = dir_to_move[dir_x][-dir_y];
            } else {
                // dist_y == 0, dist_x == 2
                move = dir_to_move[0][Math.random() > 0.5 ? 1 : -1]
            }
            setMsg(move.toString() + fight_count.toString());
            return move;
        } else if (dist === 1) {
            if (move !== -1) {
                // Kill
                setMsg(move.toString() + fight_count.toString());
                return move;
            } else {
                // Run away
                var move = dir_to_move[-dir_x][Math.random() > 0.5 ? 1 : -1]
                setMsg(move.toString() + fight_count.toString());
                return move;
            }
        }
    }
    return 0;
}

I'm quite happy with my bot at the moment. It has the following tactics:

  • If no enemy is around, move into a random direction. If I hit a wall or after around 33 moves, I switch direction.
  • If I see an enemy, I move towards him. (Carefully to not move onto a field, where the enemy can kill me). Once I near enough, move towards him from top or bottom and kill.
\$\endgroup\$
7
\$\begingroup\$

Blue team - Enemyeater

for (var i = 0; i < eNear.length; i++) {
    var enemy = eNear[i];
    var rx = enemy.x - x;
    var ry = enemy.y - y;
    if (rx == -1) {
        if (ry == 1) {
            return 4;
        }
        return 5;
    }
    if (rx == 0) {
        if (ry == 1) {
            return 2;
        }
        return 1;
    }
    if (rx == 1) {
        if (ry == 1) {
            return 3;
        }
        return 6;
    }
}
return Math.floor(Math.random() * 7);

This little pixel looks for enimens around it and tries to eat it, if there is no pixel around it moves in a random direction. I'm looking forward to see what other peoples comes up with.

\$\endgroup\$
5
  • \$\begingroup\$ Sorry, the last time I coded javascript was like 2 years ago. \$\endgroup\$
    – xenia
    Commented Apr 2, 2015 at 9:32
  • 2
    \$\begingroup\$ Math.floor, not Math.float! \$\endgroup\$
    – Jakube
    Commented Apr 2, 2015 at 10:52
  • 1
    \$\begingroup\$ @Jakube Or (Math.random() * 6) & 6 or (Math.random() * 6) << 0 or (Math.random() * 6) >> 0 (useful for codegolf). \$\endgroup\$ Commented Apr 2, 2015 at 11:11
  • \$\begingroup\$ Don't you need to do Math.random() * 7? I've tried a few test runs, and it seems your bot doesn't go to the bottom right. IIRC Math.random() is inclusive 0 and exclusive 1 which mean * 6 never actually is 6. \$\endgroup\$
    – Destrictor
    Commented Apr 2, 2015 at 13:34
  • 1
    \$\begingroup\$ Sorry everybody, I suck at JavaScript. \$\endgroup\$
    – xenia
    Commented Apr 2, 2015 at 21:19
7
\$\begingroup\$

Red team - Jittery Red Charger

var direction = 3;
if (getMsg(14314) === ''){
    setMsg('3');
}
if (getMsg(14314) === '3'){
    direction = 6;
    setMsg('6');
}
else if (getMsg(14314) === '4'){
    direction = 5;
    setMsg('5');
}
else if (getMsg(14314) === '5'){
    direction = 4;
    setMsg('4');
}
else if (getMsg(14314) === '6'){
    direction = 3;
    setMsg('3');
}
if(x === 0){
    setMsg('3');
}
else if(x === 127){
    setMsg('5');
}
return direction;

Red Charger only moves left and right, hoping to exploit Blue Team's inability to move in those directions. After reaching a wall, it turns around and charges in the opposite direction, hoping to blindly destroy any bots in its path.

EDIT: Red Charger just downed a gallon of energy drink and now can't stop jittering, it's hoping to use this to its advantage too. It's too caffeinated to listen to its teammates, but it is screaming out every one of its moves.

\$\endgroup\$
6
\$\begingroup\$

Blue team - LazySoldier

try {
    var state = getMsg(38671);
    if(state == null) {
       state = {direction:x==0?1:-1};
    } else {
       state = JSON.parse(state);
    }
    
    var choice = 0;    

    var escape=function(dx,dy) {
    if(dx==-1) {
       return y>0?4:5;
    } else if (dx==1) {
       return y>0?3:6;
    } else return 0;
    };

    var eat=function(dx,dy) {
       var b={'-1,-1':4, '0,-1':2,'1,-1':3,'-1,1':5,'0,1':1,'1,1':6};
       k=dx+','+dy;
       if(b[k]) {
       return b[k];
       } else return 0;
    };

    for(var i=0;i<eNear.length;i++) {
        var enemy = eNear[i];
        var dx=enemy.x-x;
        var dy=enemy.y-y;
        if(dy==0 && (dx==-1||dx==1)) {
            choice = escape(dx,dy);
            break;
        } else if(dy==-1 || dy==1) {
            choice = eat(dx,dy);
            break;
        }
    }

    if(x==0 || x==127) {
        state.direction=-state.direction;
    }

    if(choice == 0) {
       choice=state.direction==-1?2:1;
    }

    setMsg(JSON.stringify(state));
    return choice;
    
} catch(e) {
    if(console && console.error) {
        console.error(e);
    }
    return 0;
}
\$\endgroup\$
5
  • \$\begingroup\$ How many bytes long is the JSON message typically, do you know? Only the first 64 characters of the message can be stored. \$\endgroup\$
    – PhiNotPi
    Commented Apr 2, 2015 at 11:55
  • \$\begingroup\$ .. I forgot about that limitation :( \$\endgroup\$
    – dieter
    Commented Apr 2, 2015 at 12:00
  • \$\begingroup\$ I assume it would depend on the amount of opponents, as the json string includes the positions of the nearby eminems \$\endgroup\$
    – dieter
    Commented Apr 2, 2015 at 12:01
  • \$\begingroup\$ I'm changing the format right now \$\endgroup\$
    – dieter
    Commented Apr 2, 2015 at 12:02
  • 1
    \$\begingroup\$ You need to put that JSON.parse in a try/catch block or something... it's causing way too many errors. \$\endgroup\$
    – 12Me21
    Commented Apr 30, 2018 at 18:05
6
\$\begingroup\$

Blue Team - Mass Killer

var i, j, enemies = [];
var DIRECTIONS = [4, 2, 3, 5, 1, 6];

// initialize 5x5 surroundings
for (i = 0; i < 5; i++) { 
    enemies[i] = [];
    for (j = 0; j < 5; j++) {
        enemies[i][j] = 0;
    }
}

// get amounts of enemies there
for (i = 0; i < eNear.length; i++) {
    var xOff = eNear[i].x - x + 2;
    var yOff = eNear[i].y - y + 2;
    if (xOff >= 0 && xOff <= 4 && yOff >= 0 && yOff <= 4) {
        enemies[yOff][xOff]++;
    }
}

// get maximum amount of direct neighbours, where I can move
var max = 0, index = -1;
// check the triple above
for (i = 0; i < 3; i++) { 
    if (enemies[1][i+1] > max) {
        max = enemies[1][i+1];
        index = i;
    }
}
// check the triple below
for (i = 0; i < 3; i++) { 
    if (enemies[3][i+1] > max) {
        max = enemies[3][i+1];
        index = i + 3;
    }
}

// if there is any reachable enemy, stomp on where the biggest amount of them is 
if (max > 0) {
    return DIRECTIONS[index];
}

// otherwise, if enemy is near (though unreachable), try to move that I am above or below him
var unreachable = [];
unreachable[4] = enemies[0][1] + enemies[2][1]; // NW (north west)
unreachable[3] = enemies[0][3] + enemies[2][3]; // NE
unreachable[5] = enemies[4][1] + enemies[2][1]; // SW
unreachable[6] = enemies[4][3] + enemies[2][3]; // SE
unreachable[2] = enemies[0][2];                 // N
unreachable[1] = enemies[4][2];                 // S

max = 0, index = 0;
for (i = 1; i <= 6; i++) {
    if (unreachable[i] > max) {
        max = unreachable[i];
        index = i;
    }
}

if (max > 0) {
    return index;
}

// if no one is near, let's move randomly
return Math.round(Math.random() * 6);

I guess pretty straightforward tactic. I count enemies directly reachable by me (I suppose there will be tons of them :) ) and kill the biggest amount. If there are none, at least I'll try to shield myself by stepping above or below the biggest amount of enemies hoping for killing them next step.

I gave up taking walls into account, so I just ignore them. It's quite long anyway.

I was unable to test/run this code, so there will be many bugs.

\$\endgroup\$
5
\$\begingroup\$

Blue Team - WatchDog

var me = 38403;
var currentOwner = parseInt(getMsg(me));
var deltas = {1:{x:0,y:1},2:{x:0,y:-1},3:{x:1,y:-1},4:{x:-1,y:-1},5:{x:-1,y:1},6:{x:1,y:1}};
var check_danger = function(ennemi){
    for(var i in deltas){
        if(Math.abs(ennemi.x-x-deltas[i].x)<3 && Math.abs(ennemi.y-y-deltas[i].y)<2){
            delete deltas[i];
        }
    }
}
if(eNear.length > 0){
    for(var i in eNear){
        check_danger(eNear[i]);
    }
}
for(var i in deltas){
    if(x+deltas[i].x>126 || x+deltas[i].x<1 || y+deltas[i].y>126 || y+deltas[i].y<1)
        delete deltas[i];
}
if(!isNaN(currentOwner) && getMsg(currentOwner)!='X'){
    var Owner;
    if(tNear.length > 0){
        for(var i in tNear){
            if(tNear[i].id == currentOwner)
                Owner=tNear[i];
        }
    }
    if(Owner){
        var min=32;
        var choosen;
        var keys = Object.keys(deltas);
        if(keys.length>0){
            for(var i in deltas){
                var value = Math.abs(Owner.x-x-deltas[i].x)+Math.abs(Owner.y-y-deltas[i].y);
                if(value<min){
                    min=value;
                    choosen=i;
                }
            }
            if(min>0)
                return parseInt(choosen);
        }
    }
}
if(tNear.length > 0){
    setMsg(""+tNear[0].id);
}
var keys = Object.keys(deltas);
if(keys.length>0){
    if(eNear.length>0){
        var max=0;
        var choosen;
        for(var i in deltas){
            var value = Math.abs(eNear[0].x-x-deltas[i].x)+Math.abs(eNear[0].y-y-deltas[i].y);
            if(value>max){
                max=value;
                choosen=i;
            }
        }
        if(max>5)
            return parseInt(choosen);
    }
}
var deltas = {1:{x:0,y:1},2:{x:0,y:-1},3:{x:1,y:-1},4:{x:-1,y:-1},5:{x:-1,y:1},6:{x:1,y:1}};
if(eNear.length>0){
    var min=32;
    var choosen;
    for(var i in deltas){
        var value = Math.abs(eNear[0].x-x-deltas[i].x)+Math.abs(eNear[0].y-y-deltas[i].y);
        if(value<min){
            min=value;
            choosen=i;
        }
    }
    if(min==0)
        return parseInt(choosen);
}
return parseInt(keys[Math.floor(keys.length*Math.random())]);

It move randomly until it grabs an ally, if so it follows him. It tries to avoid to be killed, and kill if he can. Sorry for the horrible code, I went straight and forgot about refactoring. I'll try to imprive the readability if I have the time :)

\$\endgroup\$
5
\$\begingroup\$

Red Team - Seeker Commander

var myself = 29354;

//Adjust eNear to account for any friendly information, using Lazy Slayer format

//Automatically add Lazy Slayer to list, even if out of range
var mytNear = [{
    id: 14732
}].concat(tNear);

var myeNear = [].concat(eNear);
var commandable = [];
var orders = [];

for (var i = 0; i < mytNear.length; i++) {
    try {
        var msg = getMsg(mytNear[i].id);
        var enemies = undefined;
        if (msg.indexOf('"m":') !== -1) {
            commandable.push(mytNear[i]);
        }
        if (msg.indexOf(myself) !== -1) {
            var j = msg.indexOf(myself)+(myself+' ').length;
            for (; j < msg.length; j++) {
                var order = parseInt(msg.substr(j,1));
                if (order) {
                    orders.push(order);
                    break;
                }
            }
        }
        if (msg.indexOf('"e":') !== -1) {
            var enemies = msg.substr(msg.indexOf('"e":')+5).split('"')[0];
            if(!enemies) continue;
            if(enemies.charCodeAt(j) > (32+127)) {
                for (var j = 0; j < enemies.length-1; j+=2) {
                    myeNear.push({
                        x: enemies.charCodeAt(j)-174,
                        y: enemies.charCodeAt(j+1)-174,
                    });
                }
            } else {
                for (var j = 0; j < enemies.length-1; j+=2) {
                    myeNear.push({
                        x: enemies.charCodeAt(j)-32,
                        y: enemies.charCodeAt(j+1)-32,
                    });
                }
            }
        }
    } catch (e) {}
}

var calculateDistance = function(x1, y1, x2, y2) {
    return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
};

var iAmInDanger = function(meX, meY, himX, himY) {
    return (Math.abs(meY - himY) === 1 && Math.abs(meX - himX) <= 1);
};

var iCanKillHim = function(meX, meY, himX, himY) {
    return (Math.abs(meX - himX) === 1 && Math.abs(meY - himY) <= 1);
};

var getMove = function(x, y, tNear, eNear, messages) {
    var minimalDistanceToFriend = 2;
    var chosenMove = null;
    var newDistanceToFriend = null;
    var minimalVerticalDistanceToEnemy = null,
        minimalHorizontalDistanceToEnemy = null;
    var closestFriend = null;
    var closestEnemy = null;
    var possibleVictims = [];
    var possibleMoves = [{
        newX: x,
        newY: y
    }, {
        newX: x + 1,
        newY: y
    }, {
        newX: x - 1,
        newY: y
    }, {
        newX: x + 1,
        newY: y - 1
    }, {
        newX: x - 1,
        newY: y - 1
    }, {
        newX: x - 1,
        newY: y + 1
    }, {
        newX: x + 1,
        newY: y + 1
    }];

    for (i = 0; i < possibleMoves.length; i++) {
        if (possibleMoves[i].newX < 0 || possibleMoves[i].newY < 0 || possibleMoves[i].newX > 127 || possibleMoves[i].newY > 127) {
            possibleMoves[i] = null;
        }
    }

    for (var i = 0; i < eNear.length; i++) {
        if (closestEnemy === null || calculateDistance(x, y, closestEnemy.x, closestEnemy.y) > calculateDistance(x, y, eNear[i].x, eNear[i].y)) {
            closestEnemy = eNear[i];
        }
        if (Math.abs(x - eNear[i].x) <= 2 && Math.abs(y - eNear[i].y) <= 2) {
            possibleVictims.push(eNear[i]);
        }
    }

    for (i = 0; i < tNear.length; i++) {
        if (closestFriend === null || calculateDistance(x, y, closestFriend.x, closestFriend.y) > calculateDistance(x, y, tNear[i].x, tNear[i].y)) {
            closestFriend = tNear[i];
        }
    }

    //If moving to the spot would put me in danger, don't do it
    for (i = 0; i < possibleMoves.length; i++) {
        for (var j = 0; j < possibleVictims.length; j++) {
            if (possibleMoves[i] !== null && iAmInDanger(possibleMoves[i].newX, possibleMoves[i].newY, possibleVictims[j].x, possibleVictims[j].y)) {
                possibleMoves[i] = null;
            }
        }
    }

    //If moving to the spot kills an enemy, do it now
    for (i = 0; i < possibleMoves.length; i++) {
        for (j = 0; j < possibleVictims.length; j++) {
            if (possibleMoves[i] !== null && possibleMoves[i].newX === possibleVictims[j].x && possibleMoves[i].newY === possibleVictims[j].y) {
                messages[2] = 0;
                return i;
            }
        }
    }

    //Enemy in sight
    if (possibleVictims.length > 0) {
        //This can only occur when they are in my blind spot
        if (iAmInDanger(x, y, possibleVictims[0].x, possibleVictims[0].y)) {
            if (closestFriend !== null) {
                for (i = 0; i < possibleMoves.length; i++) {
                    if (possibleMoves[i] !== null) {
                        var distance = calculateDistance(possibleMoves[i].newX, possibleMoves[i].newY, closestFriend.x, closestFriend.y);
                        if (newDistanceToFriend === null || (distance < newDistanceToFriend && distance >= minimalDistanceToFriend)) {
                            newDistanceToFriend = distance;
                            chosenMove = i;
                        }
                    }
                }
                messages[2] = 0;
                setMessage();
                return chosenMove;
            } else {
                var aggressiveMoves = [];
                var randomMoves = [];

                for (i = 0; i < possibleMoves.length; i++) {
                    if (possibleMoves[i] !== null) {
                        if (iCanKillHim(possibleMoves[i].newX, possibleMoves[i].newY, possibleVictims[0].x, possibleVictims[0].y)) {
                            aggressiveMoves.push(i);
                        }
                        randomMoves.push(i);
                    }
                }
                var approachCount = messages[2] || 0;
                if (approachCount < 5 && aggressiveMoves.length > 0) {
                    messages[2] = approachCount + 1;
                    chosenMove = aggressiveMoves[Math.floor(Math.random() * aggressiveMoves.length)];
                    return chosenMove;
                } else {
                    chosenMove = randomMoves[Math.floor(Math.random() * randomMoves.length)];
                    return chosenMove;
                }
            }
        }

    }

    //Move towards closest enemy
    if (closestEnemy != null) {
        for (i = 1; i < possibleMoves.length; i++) {
            if (possibleMoves[i] !== null) {
                var verticalDistance = Math.abs(possibleMoves[i].newY - closestEnemy.y);
                var horizontalDistance = Math.abs(possibleMoves[i].newX - closestEnemy.x);
                if (minimalVerticalDistanceToEnemy === null || verticalDistance <= minimalVerticalDistanceToEnemy) {
                    if (minimalVerticalDistanceToEnemy !== null && verticalDistance === minimalVerticalDistanceToEnemy) {
                        if (minimalHorizontalDistanceToEnemy === null || horizontalDistance <= minimalHorizontalDistanceToEnemy) {
                            minimalHorizontalDistanceToEnemy = horizontalDistance;
                            chosenMove = i;
                        }
                    } else {
                        minimalVerticalDistanceToEnemy = verticalDistance;
                        minimalHorizontalDistanceToEnemy = horizontalDistance;
                        chosenMove = i;
                    }
                }
            }
        }
        messages[2] = 0;
        return chosenMove;
    }

    //Take the order
    for (var i = 0; i < orders.length; i++) {
        var order = orders[i].m || orders[i];
        if (possibleMoves[order]) {
            return orders;
        }
    }

    var seekStatus = messages[3] || 0;
    var seekCount = messages[4] || 0;
    seekStatus = parseInt(seekStatus, 10);
    seekCount = parseInt(seekCount, 10);

    switch (seekStatus) {
        case 0:
            if (x < 16) {
                seekCount = 0;
                if (y > 111) {
                    seekStatus = 4;
                } else {
                    seekStatus = 1;
                }
            } else {
                chosenMove = 2;
            }
            break;
        case 1:
            seekCount++;
            if (y > 111 || seekCount > 31) {
                seekStatus = 2;
            } else {
                if (seekCount % 2 === 0) {
                    chosenMove = 5;
                } else {
                    chosenMove = 6;
                }
            }
            break;
        case 2:
            if (x > 111) {
                seekCount = 0;
                if (y > 111) {
                    seekStatus = 4;
                } else {
                    seekStatus = 3;
                }
            } else {
                chosenMove = 1;
            }
            break;
        case 3:
            seekCount++;
            if (y > 111 || seekCount > 31) {
                seekStatus = 0;
            } else {
                if (seekCount % 2 === 0) {
                    chosenMove = 5;
                } else {
                    chosenMove = 6;
                }
            }
            break;
        case 4:
            seekCount++;
            if (y < 16) {
                if (x > 63) {
                    seekStatus = 0;
                } else {
                    seekStatus = 2;
                }
            } else {
                if (seekCount % 2 === 0) {
                    chosenMove = 3;
                } else {
                    chosenMove = 4;
                }
            }
            break;
    }

    messages[2] = 0;
    messages[3] = seekStatus;
    messages[4] = seekCount;
    return chosenMove;
}

var messageObj = JSON.parse('{'+getMsg(myself)+'}');
if (!messageObj.$) {
    messageObj = {$:0};
}
var approachCount = (messageObj.$ & 7);
var seekStatus = ((messageObj.$ >> 3) & 7);
var seekCount = ((messageObj.$ >> 6));
var messages = [x, y, approachCount, seekStatus, seekCount];
var myMove = getMove(x, y, mytNear, myeNear, messages);
var msg = '"$":'+(messages[2] + (messages[3]<<3) + (messages[4]<<6)+',"m":'+myMove);
orders.length = 0;

//Issue commands to my allies
for (var i = 0; i < commandable.length; i++) {
    var ally = commandable[i];
    var command = getMove(ally.x, ally.y, tNear, myeNear, messages);
    var cmdStr = ',"'+ally.id+'":{"m":"'+command+'","a":1,"id":'+myself+'}'
    if (msg.length + cmdStr.length < 64) {
        msg += cmdStr;
    }
}

if (msg.length+9 < 64) {
    //Add my list of enemies
    var enemies = "";
    for(var i = 0; i < myeNear; i++) {
        if (msg.length+enemies.length+9 > 64) {
            break;
        }
        enemies+=String.fromCharCode(eNear[i].x+174)+String.fromCharCode(eNear[i].y+174);
    }
    msg += ',"e":"'+enemies+'"';
}

setMsg(msg);
return myMove;

This one is a copy of Minos' SeekerBot with a few modifications.

  • Compressed internal memory for better message distribution "$":[seekmode]

  • Reads enemy positions from allies using Lazy Slayer's JSON format "e":"[positions]"; accepts [positions] offset by both 32 and 174

  • Reports enemy positions in Lazy Slayer's JSON format, "e":"[positions]" offset by 174

  • Reports last move with "m":[move] to indicate that this bot can be commanded

  • Issues commands to other bots using "[ally_id]":{"m":[move],"a":1,"id":29354}. Command uses same seeker algorithm except at ally's location. If other bots listen to these orders, they should group together and hunt in a pack. Orders only given if ally's message includes "m":

  • Follows commands from other bots, such as: "29354":[move] or "29354":{"m":[move]. Commands are only followed when no enemies are in range and no other allies are reporting enemies

\$\endgroup\$
2
  • \$\begingroup\$ I would recommend incrementing the coordinates by 174 instead of 32. Some messages that Lazy slayer was sending were like: ""a":0,"o":[122,70],"m":0,"e":"f"". You could join us in chat if you want too. \$\endgroup\$ Commented Apr 3, 2015 at 22:22
  • \$\begingroup\$ Alas, I don't have time. I'll check back again later tonight, but that's going to be it. I've updated the post to use the 174 encoding, both for posting and parsing. \$\endgroup\$
    – Wasmoo
    Commented Apr 3, 2015 at 23:03
5
\$\begingroup\$

Red Team - BouncerBot

function getDir(diff){
  return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0);
}
function randInt(max){
  return Math.ceil(Math.random() * max);
}
var me = 29750;
var moves = [
  [4,3,3],
  [2,0,1],
  [5,5,6]
]; // Directions: -1 = up/left, 1 = down/right, 0 = none
if(x === 0){
  moves[0] = [3,3,3];
  moves[2] = [6,6,6];
} else if(x == 127){
  moves[0] = [4,4,4];
  moves[2] = [5,5,5];
}
for(var i in eNear){
  var xDiff = eNear[i].x - x,
      yDiff = eNear[i].y - y;
  if(xDiff >= -1 && xDiff <= 1 && yDiff >= -1 && yDiff <= 1){
    // If the enemy is directly adjacent, attack
    setMsg('');
    return moves[yDiff + 1][xDiff + 1];
  }
}
if(eNear.length > 0){
  var xDiff = eNear[0].x - x,
      yDiff = eNear[0].y - y;
  // If it can't attack, move toward the enemy.
  if(Math.abs(yDiff) == 2){
    if(xDiff >= -2 && xDiff <= 0) return 1;
    else if(xDiff == 2 || xDiff === 1) return 2;
  }
  return moves[getDir(yDiff) + 1][getDir(xDiff) + 1];
}
var msg = getMsg(me) || '',
    newDir = parseInt(msg);
if(msg === ''){
  newDir = randInt(4) + 2;
}
var isEndgame = move > 512;
     if(x === 0 || (isEndgame && x < 11)) newDir = (msg == 4) ? 3 : 6;
else if(x == 127 || (isEndgame && x > 116)) newDir = (msg == 3) ? 4 : 5;
else if((!isEndgame && y < 11) || y === 0) newDir = (msg == 4) ? 5 : 6;
else if((!isEndgame && y > 116) || y == 127) newDir = (msg == 5) ? 4 : 3;
if(newDir != msg) setMsg(newDir.toString());
return newDir;

My bot bounces from wall to wall (not exactly, so it covers different ground) looking for enemies. If it gets one in its range, it attacks, drags them toward a wall, and tries to take them out (think bouncer at a club).

\$\endgroup\$
5
\$\begingroup\$

Red Team - SideKick

var possibleMoves = [
      {newX: x, newY: y, value: 1},
      {newX: x, newY: y + 1, value: 1},
      {newX: x, newY: y - 1, value: 1},
      {newX: x + 1, newY: y - 1, value: 1},
      {newX: x - 1, newY: y - 1, value: 1},
      {newX: x - 1, newY: y + 1, value: 1},
      {newX: x + 1, newY: y + 1, value: 1}
];

var isDeadly = function(myX, myY, eX, eY) {
      return (Math.abs(myY - eY) === 1 && Math.abs(myX - eX) <= 1);
}

//stay near helpful friends!
if (tNear.length > 0) {
      for (var i = 0; i < tNear.length; i++) {
      if (Math.abs(tNear[i].x - x) > 2) {
            if (tNear[i].x > x) {
                  possibleMoves[3].value = possibleMoves[3].value + 5;
                  possibleMoves[1].value = possibleMoves[1].value + 5;
                  possibleMoves[6].value = possibleMoves[6].value + 5;
            } else {
                  possibleMoves[4].value = possibleMoves[4].value + 5;
                  possibleMoves[2].value = possibleMoves[2].value + 5;
                  possibleMoves[5].value = possibleMoves[5].value + 5;
            }
      }
      if (Math.abs(tNear[i].y - y) > 2) {
            if (tNear[i].y > y) {
                  possibleMoves[5].value = possibleMoves[5].value + 5;
                  possibleMoves[6].value = possibleMoves[6].value + 5;
            } else {
                  possibleMoves[4].value = possibleMoves[4].value + 5;
                  possibleMoves[3].value = possibleMoves[3].value + 5;
            }
      }
      }
}
//chase those enemies!
if (eNear.length > 0) {
      for (var i = 0; i < eNear.length; i++) {
      if (Math.abs(eNear[i].x - x) > 2) {
            if (eNear[i].x > x) {
                  possibleMoves[3].value = possibleMoves[3].value + 5;
                  possibleMoves[1].value = possibleMoves[1].value + 5;
                  possibleMoves[6].value = possibleMoves[6].value + 5;
            } else {
                  possibleMoves[4].value = possibleMoves[4].value + 5;
                  possibleMoves[2].value = possibleMoves[2].value + 5;
                  possibleMoves[5].value = possibleMoves[5].value + 5;
            }
      }
      if (Math.abs(eNear[i].y - y) > 2) {
            if (eNear[i].y > y) {
                  possibleMoves[5].value = possibleMoves[5].value + 5;
                  possibleMoves[6].value = possibleMoves[6].value + 5;
            } else {
                  possibleMoves[4].value = possibleMoves[4].value + 5;
                  possibleMoves[3].value = possibleMoves[3].value + 5;
            }
      }
      }
}

//walls
if (x === 127){
       possibleMoves[3] = null;
       possibleMoves[1] = null;
       possibleMoves[6] = null;
}
if (x === 0){
       possibleMoves[4] = null;
       possibleMoves[2] = null;
       possibleMoves[5] = null;
}
if (y === 0){
       possibleMoves[3] = null;
       possibleMoves[4] = null;
}
if (y === 127){
       possibleMoves[5] = null;
       possibleMoves[6] = null;
}

//deadly enemies
for (var i = 0; i < eNear.length; i++) {
      for (var j = 0; j < possibleMoves.length; j++) {
            if (possibleMoves[j] !== null && isDeadly(possibleMoves[j].newX, possibleMoves[j].newY, eNear[i].x, eNear[i].y)) {
                  possibleMoves[j] = null;
            }
      }
}
      
var bestMoves = [];
for (var i = 0; i < possibleMoves.length; i++)
{
      if (possibleMoves[i] !== null) {
            if (bestMoves.length === 0 || possibleMoves[i].value > possibleMoves[bestMoves[0]].value) {
                  bestMoves = [i];
            }
            else if (possibleMoves[i].value === possibleMoves[bestMoves[0]].value) {
                  bestMoves.push(i);
            }
      }
}
var returnValue = bestMoves[Math.floor(Math.random()*(bestMoves.length))];
      
return returnValue;

Likes to follow teammates around, good thing there's plenty of them!

\$\endgroup\$
2
  • \$\begingroup\$ Funny to note, when this teams up with Wasmoo's bot, it can sometimes defeat the blue duo. \$\endgroup\$
    – Etheryte
    Commented Apr 5, 2015 at 19:57
  • 1
    \$\begingroup\$ @Nit I haven't witnessed it; but I'm guessing mine becomes a distraction for Wasmoo's to kill them? Wish I could add to it now; I'd try predicting several turns ahead and see what kind of suicide - tactics might work against the duo. \$\endgroup\$ Commented Apr 6, 2015 at 20:15
5
\$\begingroup\$

Blue Team - indecisive magnet

var Mul = 4;
var Mu = 2;
var Mur = 3;
var Mdl = 5;
var Md = 1;
var Mdr = 6;
var Ms = 0;
var M = [Ms,Md,Mu,Mur,Mul,Mdl,Mdr];
var C =  [Mul,Mur,Mdl,Mdr];
var Mc = [{x:0,y:0},{x:0,y:1},{x:0,y:-1},{x:1,y:-1},{x:-1,y:-1},{x:-1,y:1},{x:1,y:1}];
/* If one or more enemies */
var nearEnemies = 0;
for(var i=0;i<eNear.length;i++){
    if(Math.abs(eNear[i].x-x)+Math.abs(eNear[i].y-y)<5){
        nearEnemies++;
    }
}
if(nearEnemies >0){
    //First check whether I can beat the enemy
    for(var i=0;i<eNear.length;i++){
        for(var j=0;j<7;j++){
            if(x+Mc[j].x == eNear[i].x && y+Mc[j].y == eNear[i].y){
                return j;
            }
        }
    }
    
    // Else advanced tactics
    function inRangeOfNEnemies(mx,my,eNear){
        var n=0;
        for(var i=0;i<eNear.length;i++){
            if( Math.abs(my-eNear[i].y)<=1 && Math.abs(mx-eNear[i].x)==1 ){
                n=n+1;
            }
            
        }
        return n;
    }

    //check all all possible moves:
    var moveDangerousness = new Array(7);;
    for(var i=0;i<7;i++)moveDangerousness[i]=1/(Math.abs(x+Mc[i].x-64)+Math.abs(y+Mc[i].y-64)+1);
    //calculate dangerouseness
    for(var i=0;i<7;i++){
        moveDangerousness[i] += inRangeOfNEnemies(x+Mc[i].x,y+Mc[i].y,eNear);
    }
    //mind walls
    for(var i=0;i<7;i++){
        if(x+Mc[i].x<0 ||  x+Mc[i].x>127 || y+Mc[i].y<0 ||  y+Mc[i].y>127 ){
            moveDangerousness[i] = 9999;
        }   
    }

    var leastDangerous = moveDangerousness.indexOf(Math.min.apply(Math,moveDangerousness));
    return leastDangerous;
} else if (eNear.length>3){ //run away from enemies
    var xmean = 0;
    var ymean = 0;
    for(var i=0;i<eNear.length;i++){
        xmean += eNear[i].x*1.0/eNear.length;
        ymean += eNear[i].y*1.0/eNear.length;       
    }
    var dx = x-xmean;
    var dy = y-ymean;
    if(dx >0){
        if(dy>0){
            return Mdr;
        } else {
            return Mur;
        }
    } else {
        if(dy>0){
            return Mdl;
        } else {
            return Mul;
        }
    }

} else {//* if there are no enemies *//
    //walk pattern until you find friend, then folloow friend
    var dx = 999; var dy = 999;
    if(tNear.length>0){
        for(var i=0;i<tNear.length;i++){
            if(Math.abs(dx)+Math.abs(dy) > Math.abs(tNear[i].x-x)+Math.abs(tNear[i].y-y)){
                dx = tNear[i].x-x;
                dy = tNear[i].y-y;
            }
        }
    } else {
        dx = 64-x+10*(Math.random()-0.5);
        dy = 64-y+10*(Math.random()-0.5);
    }

    if(dx >0){
        if(dy>0){
            return Mdr;
        } else {
            return Mur;
        }
    } else {
        if(dy>0){
            return Mdl;
        } else {
            return Mul;
        }
    }
}

It has multiple strategies: If it can beat an enemy immediately it will do so, and it will run away from groups of enemies if they are far away enough, else it will fight. Other than that it is just looking for team members and trying to follow them.

\$\endgroup\$
4
\$\begingroup\$

Blue Team - Fetch [38953]

var me = 38953;
var msg = getMsg(me);
var register = msg ? JSON.parse(msg) : {};
var prevDanger = 0;
var danger;

var eScope = eNear;
var myX = x;
var myY = y;
var put = setMsg;
var get = getMsg;

function kill(){
  var move = -1;
  if(!eScope){return -1;}

  eScope.forEach(function(e){
    if(move > -1){return move;}

    var xDist = Math.abs(e.x-myX);
    var yDist = Math.abs(e.y-myY);

    if(xDist < 2 && yDist < 2){
      if(e.x == myX){
        if(e.y == myY-1){move = 2;}
        else if(e.y == myY+1){move = 1;}
      }
      else if(e.x == myX-1){
        if(e.y == myY-1){move = 4;}
        else if(e.y == myY+1){move = 5;}
      }
      else if(e.x == myX+1){
        if(e.y == myY-1){move = 3;}
        else if(e.y == myY+1){move = 6;}
      }
    }
  });

  return move;
}

function live(){
  var move = -1;
  if(!eScope){return -1;}
  var topHalf = (myY <= 64);

  eScope.forEach(function(e){
    if(move > 0){return move;} //0 on purpose; we might find a better escape

    var xDist = Math.abs(e.x-myX);
    var yDist = Math.abs(e.y-myY);

    if(xDist + yDist < 5){move = 0;}  //uh oh!  Stand still!

    if(e.y == myY){
      if(e.x == myX-1){move = (topHalf ? 5 : 4);}
      else if(e.x == myX+1){move = (topHalf ? 6 : 3);}
    }
  });

  return move;
}

function evalDanger(){
  danger = 0;

  if(register){prevDanger = register.d;}

  eScope.forEach(function(e){
    var xDist = Math.abs(e.x-myX);
    var yDist = Math.abs(e.y-myY);
    danger += ((1/yDist) * (1/xDist));
  });

  register.d = danger;
  put(JSON.stringify(register));
  return danger;
}

function distract(){
  //run to the edge if safe, to the middle if not
  var safe = (danger <= prevDanger && danger < .01);

  var topHalf = myY <= 64;
  var leftSide = myX <= 64;

  //lazy init to 'explore' mode
  if(!register.e){register.e = 1;}

  //lazy init to whatever corner we're in
  if(!register.f){
    register.f = topHalf ? leftSide ? 4 : 3 : leftSide ? 5 : 6;
  }

  //turn 'explore' on (1) or off (2);
  //if 'off' but hit 'home base', seek a corner
  if(register.e == 2 && ((myY > 54 && myY < 74) || (myX > 54 && myX < 74))){
    register.e = 1
    register.f = Math.floor(Math.random()*4)+3;
  }
  //if on the outskirts, go back to base
  if(myY < 10 || myY > 115 || myX < 10 || myX > 115){register.e = 2;}

  put(JSON.stringify(register));

  if(topHalf){
    if(leftSide){
      if(!safe || register.e == 2){return 6;}
    }
    else{
      if(!safe || register.e == 2){return 5;}
    }
  }
  else {
    if(leftSide){
      if(!safe || register.e == 2){return 3;}
    }
    else{
      if(!safe || register.e == 2){return 4;}
    }
  }
  return register.f;
}

evalDanger();
register.x = myX;
register.y = myY;

var whee = kill();
if(whee > -1){
    return whee;
}

whee = live();
if(whee > -1){
    return whee;
}

whee = distract();
return whee;

[edits: turns out it works a LOT better when I use my actual id and not -1!]

A dumb little bot that runs around the board, doing his best to attract attention and draw chasers, and then run to the middle where (hopefully) he'll find somebody to help him, or he'll just stall the chaser and stop him from hunting.

Doesn't seem to make a huge impact on overall score due to how powerful the Blue tagteam are, but at the very least I didn't make things worse!

Shout in the next 8 hours if you want me to add anything useful to my message.

\$\endgroup\$
3
  • \$\begingroup\$ The middle is not the best place to draw attention; many other bots seem to circle the edges when looking for enemies to fight. Maybe your bot should circle the middle? \$\endgroup\$
    – user36219
    Commented Apr 5, 2015 at 10:35
  • \$\begingroup\$ It's easier to evade in the middle than the corners, but if you're saying that I might not find teammates in the middle... well, you're right; my #1 cause of death right now is that red gets an ally before I do. For the next KotH, I'm ready to greatly improve the ability to call other bots for help when I've found a target! \$\endgroup\$ Commented Apr 5, 2015 at 12:58
  • \$\begingroup\$ I'm working on a bot called beacon; it's msg will contain the positions of every enemy and team member on the field, and can then be used by other bots to track and kill said enemies,or latch on if teammates. \$\endgroup\$
    – user36219
    Commented Apr 5, 2015 at 13:07
4
\$\begingroup\$

Blue Team - PatrolBot

var directionMap = {'0,0':0, '0,1':1, '1,1':6, '1,0':null, '1,-1':3, '0,-1':2, '-1,-1':4, '-1,0':null, '-1,1':5},
    direction = parseInt((getMsg(38951) || getMsg(-1) || '').slice(0, 1));

if (typeof direction !== 'number' || isNaN(direction)) direction = 0;

if (!tNear.length && !eNear.length) {
    if (!isDirection(direction) || isNearWall(12, x, y)) {
        direction = moveTowardsCoords(64, 64, x, y, directionMap, eNear);
    } else {
        direction = direction;
    }
} else if (eNear.length) {
    if (canKill(x, y, eNear, directionMap)) {
        direction = kill(x, y, eNear, directionMap);
    } else if (isNearEnemy(x, y, eNear)) {
        direction = moveToBetterPosition(x, y, eNear, directionMap);
    } else if (tNear.length + 1 >= eNear.length) {
        direction = moveTowardsEnemy(eNear, x, y, directionMap);
    } else {
        if (tNear.length) {
            direction = moveTowardsTeam(tNear, x, y, directionMap);
        } else {
            direction = moveAwayFromEnemy(eNear, x, y);
        }
    }
} else if (tNear.length && Math.random() > 0.8) {
    direction = moveTowardsTeam(tNear, x, y, directionMap);
}

setMsg((direction || 0).toString());
return direction;

function find(arr, func) {
    for (var i = 0; i < arr.length; i++) {
        if (func(arr[i])) {
            return arr[i];
        }
    }
}

function invert(obj) {
    var result = {},
        key;

    for (key in obj) {
        if (obj.hasOwnProperty(key)) {
            result[obj[key]] = key;
        }
    }

    return result;
}

function isDirection(direction) {
    return direction >= 1 && direction <= 6;
}

function isNearWall(margin, x, y) {
    return x < margin || x > (127 - margin) || y < margin || y > (127 - margin);
}

function getDistance(x1, y1, x2, y2) {
    var xd, yd;

    xd = x2 - x1;
    xd = xd * xd;

    yd = y2 - y1;
    yd = yd * yd;

    return Math.sqrt(xd + yd);
}

function getCoordDiff(x1, y1, x2, y2) {
    return [x1 - x2, y1 - y2];
}

function identifyClosest(arr, x, y) {
    var lowest = 128;

    arr = arr.map(function(i) {
        i.distance = getDistance(x, y, i.x, i.y);
        return i;
    });

    arr.forEach(function(i) {
        if (i.distance < lowest) {
            lowest = i.distance;
        }
    });

    return find(arr, function(i) {
        return i.distance === lowest;
    });
}

function identifyClosestTeam(tNear, x, y) {
    return identifyClosest(tNear, x, y);
}

function identifyClosestEnemy(eNear, x, y) {
    return identifyClosest(eNear, x, y);
}

function kill(x, y, eNear, directionMap) {
    var enemy = identifyClosestEnemy(eNear, x, y);
    return enemy ? directionMap[getCoordDiff(enemy.x, enemy.y, x, y).toString()] : 0;
}

function canKill(x, y, eNear, directionMap) {
    return !!kill(x, y, eNear, directionMap);
}

function enemyCanKill(id, x, y, eNear) {
    var arr = ['1,0', '1,1', '1,-1', '-1,0', '-1,-1', '-1,1'],
        enemy = find(eNear, function(i) {
            return i.id === id;
        });

    if (!enemy) {
        return false;
    }

    return arr.indexOf(getCoordDiff(x, y, enemy.x, enemy.y).toString()) !== -1;
}

function isNearEnemy(x, y, eNear) {
    var enemy = identifyClosestEnemy(eNear, x, y);

    if (!enemy) {
        return 0;
    }

    return Math.max.apply(null, getCoordDiff(x, y, enemy.x, enemy.y).map(function(i){
        return Math.abs(i);
    })) <= 2;
}

function isIntoWall(dx, dy) {
    return dx > 127 || dx < 0 || dy > 127 || dy < 0;
}

/**
 * Picks a random direction heading towards {dx, dy}
 */
function moveTowardsCoords(destX, destY, oldX, oldY, directionMap, eNear) {
    return changeDirection(function(newX, newY) {
        return getDistance(oldX, oldY, destX, destY) - getDistance(newX, newY, destX, destY);
    }, oldX, oldY, eNear, directionMap);
}


function changeDirection (scoringFunction, x, y, eNear, directionMap) {
    var highest = 0,
        validDirections = (function() {
            var result = {};
            for (var key in directionMap) {
                if (directionMap.hasOwnProperty(key) && directionMap[key] !== null) {
                    result[key] = directionMap[key];
                }
            }
            return result;
        })(),
        coords = Object.keys(validDirections).map(function(i) {
            var result = {
                    vector: i,
                    score: 0
                },
                xy = i.split(',').map(function(term, i) {
                    return parseInt(term) + (i === 0 ? x : y);
                });

            result.x = xy[0];
            result.y = xy[1];

            result.score = scoringFunction(result.x, result.y, eNear, directionMap);

            if (result.score > highest) {
                highest = result.score;
            }

            return result;
        }),
        arr = coords.filter(function(i) {
            return i.score === highest;
        });

    var num = Math.floor(Math.random() * arr.length);

    return validDirections[arr[num].vector];
}

function moveTowards(id, x, y, tNear, eNear, directionMap) {
    var target = find([].concat(tNear, eNear), function(i) {
        return i.id === id;
    });

    if (target) {
        return moveTowardsCoords(target.x, target.y, x, y, directionMap, eNear);
    } else {
        return 0;
    }
}

function moveTowardsEnemy(eNear, x, y, directionMap) {
    var enemy = identifyClosestEnemy(eNear, x, y);

    return enemy ? moveTowards(enemy.id, x, y, [], eNear, directionMap) : 0;
}

function moveTowardsTeam(tNear, x, y, directionMap) {
    var team = identifyClosestTeam(tNear, x, y);

    return team ? moveTowards(team.id, x, y, tNear, [], directionMap) : 0;
}

function moveAwayFromEnemy(eNear, x, y) {
    var oppositeMap = {
        0: 0,
        1: 2,
        2: 1,
        3: 5,
        4: 6,
        5: 3,
        6: 4
    };
    return oppositeMap[moveTowardsEnemy(eNear, x, y, directionMap)];
}

/**
 * Gives points to each move based on three metrics:
 * 
 * 2) will not cause us to be killed next turn
 * 1) will let us kill next turn
 * 
 * Then randomly picks from the highest scoring moves
 */
function moveToBetterPosition(x, y, eNear, directionMap) {
    return changeDirection(function(x, y, eNear, directionMap) {
        var score = 0;

        if (canKill(x, y, eNear, directionMap)) {
            score += 1;
        }

        if (!eNear.some(function(e) {
                return enemyCanKill(e.id, x, y, eNear);
            })) {
            score += 2;
        }

        if (isIntoWall(x, y)) {
            score = 0;
        }

        return score;
    }, x, y, eNear, directionMap);
}

The code is sort of self-documenting. Things that could be done to improve PatrolBot

  • Refactor - Move everything into IIFE, use variables from closure instead of passing them twenty majillion times.
  • Add if (canBeKilled() || isInWall()) { moveToBetterPosition() } just before return.
  • Clean up bad behaviour when following a team member next to a wall.
  • Avoid landing on team members.
  • Move away from enemy if continuously engaged for 200 turns.
\$\endgroup\$
1
2
\$\begingroup\$

BLUE TEAM - 1 Point Awesome

//  1PointAwesome by Grant Davis

var myid=38941; //My ID for getMsg()

var result=0;
var leeway=1; //How close to follow enemy
var gForce=3; //How strongly gravity effects x/y
var futureDanger=true;
//Modifier Random Generation
var newX=Math.floor(Math.random()*3-1);
var newY=Math.floor(Math.random()*3-1);
var random10=Math.floor(Math.random()*2);

var dangerArray=[[false,false,false],[false,false,false],[false,false,false]];
var gravityX,gravityY,antiGravityX,antiGravityY;

//Sets defaults: gravity center
if(move==1){setMsg("64,64");}

//Change gravity when you have reached within 5 of gravity
if(eNear.length==0){
 if(Math.floor(Math.random()*2)==0){
  if(parseInt(getMsg(myid).split(",")[0])-x<5&&parseInt(getMsg(myid).split(",")[0])-x>-5){setMsg(Math.floor(Math.random()*32)+","+getMsg(myid).split(",")[1]);}
  if(parseInt(getMsg(myid).split(",")[1])-y<5&&parseInt(getMsg(myid).split(",")[1])-y>-5){setMsg(getMsg(myid).split(",")[0]+","+Math.floor(Math.random()*32));}
 }else{
  if(parseInt(getMsg(myid).split(",")[0])-x<5&&parseInt(getMsg(myid).split(",")[0])-x>-5){setMsg(Math.floor(Math.random()*32+96)+","+getMsg(myid).split(",")[1]);}
  if(parseInt(getMsg(myid).split(",")[1])-y<5&&parseInt(getMsg(myid).split(",")[1])-y>-5){setMsg(getMsg(myid).split(",")[0]+","+Math.floor(Math.random()*32+96));}
 }
}

//Pulls gravity from getMsg() and converts it into variables readable by the program
if(x<parseInt(getMsg(myid).split(",")[0])+Math.floor(Math.random()*30-15)){gravityX=1;antiGravityX=-1;}else{gravityX=-1;antiGravityX=1;}
if(y<parseInt(getMsg(myid).split(",")[1])+Math.floor(Math.random()*30-15)){gravityY=-1;antiGravityY=1;}else{gravityY=1;antiGravityY=-1;}

//Modifier Random Generation, Gravity bias.
if(Math.floor(Math.random()*gForce)!=0||x<31&&eNear.length==0||x>95&&eNear.length==0){newX=gravityX;}
if(Math.floor(Math.random()*gForce)!=0||y<31&&eNear.length==0||y>95&&eNear.length==0){newY=gravityY;}


//Avoid edges modifier:
//Sets gravity to 64,64 when within 32 of an edge

if(y<31&&eNear.length==0||y>95&&eNear.length==0){setMsg(getMsg(myid).split(",")[0]+",64");}
if(x<31&&eNear.length==0||x>95&&eNear.length==0){setMsg("64,"+getMsg(myid).split(",")[1]);}

//Targeting Modifier:
//Does not modify if outnumbered
//Tries to attack from above or below
//If enemy escapes: look where the enemy was last at
//Reset gravity if all targets

if(eNear.length<=tNear.length+1&&eNear.length!=0){

setMsg(eNear[0]["x"]+","+eNear[0]["y"]);
if(eNear[0]["x"]>x){newX=1;}else if(eNear[0]["x"]<x){newX=-1;}else{newX=0;}
if(eNear[0]["y"]>y+leeway){newY=-1;}else if(eNear[0]["y"]<y-leeway){newY=1;}
}



//Anti loop Modifier: Removed due to minor strategy flaw


//If I can get above or below a pixel, do it 


//If I can kill an enemy pixel, kill it
for(var ep=0;eNear.length>ep;ep+=1){

 if(eNear[ep]["x"]==x&&eNear[ep]["y"]-y==1){newY=-1;newX=0;}
 else if(eNear[ep]["x"]==x&&eNear[ep]["y"]-y==-1){newY=1;newX=0;}
 else if(eNear[ep]["x"]-x==-1){
  if(eNear[ep]["y"]-y==1){newX=-1;newY=-1;}
  else if(eNear[ep]["y"]-y==-1){newX=-1;newY=1;}
 }
 else if(eNear[ep]["x"]-x==1){
  if(eNear[ep]["y"]-y==1){newX=1;newY=-1;}
  else if(eNear[ep]["y"]-y==-1){newX=1;newY=1;}
 }
}

//Not allowed to move off screen.
if(x==0){for(var i=0;i<=2;i+=1){dangerArray[0][i]==true;}}
if(x==127){for(var i=0;i<=2;i+=1){dangerArray[2][i]==true;}}
if(y==0){for(var i=0;i<=2;i+=1){dangerArray[i][0]==true;}}
if(y==127){for(var i=0;i<=2;i+=1){dangerArray[i][2]==true;}}

var originalNewX=newX;
var originalNewY=newY;






//Double checks movement made by previous code, and then turns it into a number that Pixel Team Battlebots can read
for(var antiloop=0;futureDanger&&antiloop<20;antiloop+=1){

 futureDanger=false;


 //When bot tries to move left or right, it will move diagonal.
 if(newX!=0&&newY==0){
  newY=Math.floor(Math.random()*2);
  if(newY==0){newY=-1;}
 }


 if(eNear.length>0){ //Protocol Paranoid: When pixel attempts to move into dangerous square, The pixel will move into a different square, and recheck danger.




  for(var ep=0;ep<eNear.length;ep+=1){ //Checks for the danger level of the square pixel attempts to move in.

   if(Math.abs(eNear[ep]["x"]-(x+newX))==1 && eNear[ep]["y"]-(y-newY)<=1 && eNear[ep]["y"]-(y-newY)>=-1){
    futureDanger=true;

    dangerArray[newX+1][Math.abs(newY-1)]=true;
    if(dangerArray[1][1]==false){newX=0;newY=0;}//When attempt to move into dangerous square, do nothing
    else if(dangerArray[gravityX+1][gravityY+1]==false){newX=gravityX;newY=gravityY;}
    else if(dangerArray[antiGravityX+1][gravityY+1]==false){newX=antiGravityX;newY=gravityY;random10=1;}
    else if(dangerArray[gravityX+1][antiGravityY+1]==false){newX=gravityX;newY=antiGravityY;random10=0;}

    else if(dangerArray[antiGravityX+1][antiGravityY+1]==false){newX=antiGravityX;newY=antiGravityY;}
    else if(dangerArray[1][gravityY+1]==false){newX=0;newY=gravityY;}
    else if(dangerArray[1][antiGravityY+1]==false){newX=0;newY=antiGravityY;}
    else{newX=originalX;newY=originalY;}
   }
  }
 }else//End of Protocol Paranoid

 if(antiloop==18){newX=originalNewX;NewY=originalNewY;}

}//Big for end


if(newY==1){result=2;}else if(newY==-1){result=1;}

if(newX==1){if(result==2){result=3;}else if(result==1){result=6;}}else if(newX==-1){if(result==2){result=4;}else if(result==1){result=5;}}



return result;

The pixel's priorities:

  • Never move into dangerous spaces (out of bound spaces are considered dangerous)
  • Kill if able
  • Move toward Enemy (the first one in eNear) as long as not outnumbered
  • Move away from edges
  • Go to enemies last known location
  • Go to where gravity is located

Gravity is set to 64,64 at move 1

Gravity is set to nearest enemies location (to guide pixel to last enemy's location, if enemy escapes)

Gravity changes randomly when pixel has reached center of gravity or when near the edge

\$\endgroup\$
2
\$\begingroup\$

Red - LoyalFollower [15080]

var dangerValues = {
    killEnemy:      -110,
    killMe:          160,
    nearEnemy:       -20,
    killPair:       -200,
    friendIsThere:    30,
    outside:         999,
    nearWall:         10,
    wayToMinos:       -2,
    wayToFriend:      -1,
    wayToEnemy:       -4,
    wayToManyEnemies:  3
};

var moves = [
    {newX: x, newY: y, danger: 0},
    {newX: x + 1, newY: y, danger: 0},
    {newX: x - 1, newY: y, danger: 0},
    {newX: x + 1, newY: y - 1, danger: 0},
    {newX: x - 1, newY: y - 1, danger: 0},
    {newX: x - 1, newY: y + 1, danger: 0},
    {newX: x + 1, newY: y + 1, danger: 0}
];
var closestEnemy = null;
var closestFriend = null;

var distance = function(x1, y1, x2, y2) {
    return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
};

var meKillable = function(meX, meY, himX, himY) {
    return (Math.abs(meY - himY) === 1 && Math.abs(meX - himX) <= 1);
};

var enemyKillable = function(meX, meY, himX, himY) {
    return (Math.abs(meX - himX) === 1 && Math.abs(meY - himY) <= 1);
};

for (i = 0; i < moves.length; i++) {
    if (moves[i].newX < 0 || moves[i].newY < 0 || moves[i].newX > 127 || moves[i].newY > 127) {
        moves[i].danger = dangerValues.outside;
    }
    if (moves[i].newX === 0 || moves[i].newX === 127 || moves[i].newY === 0 || moves[i].newY === 127) {
        moves[i].danger += dangerValues.nearWall;
    }
    for (var j = 0; j < eNear.length; j++) {
        if (closestEnemy === null || distance(x, y, closestEnemy.x, closestEnemy.y) > distance(x, y, eNear[j].x, eNear[j].y)) {
            closestEnemy = eNear[i];
        }

        if (moves[i].newX === eNear[j].x && moves[i].newY === eNear[j].y) {
            if (eNear[j].id === 21487 || eNear[j].id === 2867) {
                moves[i].danger += dangerValues.killPair;
            } else {            
                moves[i].danger += dangerValues.killEnemy;
            }
        }
        if (meKillable(moves[i].newX, moves[i].newY, eNear[j].x, eNear[j].y)) {
            moves[i].danger += dangerValues.killMe;
        }
        if (enemyKillable(moves[i].newX, moves[i].newY, eNear[j].x, eNear[j].y)) {
            moves[i].danger += dangerValues.nearEnemy;
        }
    }
    for (var j = 0; j < tNear.length; j++) {
        if (closestFriend === null || distance(x, y, closestFriend.x, closestFriend.y) > distance(x, y, tNear[j].x, tNear[j].y)) {
            closestFriend = tNear[i];
        }
        if (moves[i].newX === tNear[j].x && moves[i].newY === tNear[j].y) {
            moves[i].danger += dangerValues.friendIsThere;
        }
    }
}

var bestDistanceToMinos = 200;
var minos = 38926;
var minosMsg = getMsg(minos);
var manyEnemies = eNear.length > tNear.length;
if (minosMsg !== '' && minosMsg !== undefined) {
    minosMsg = minosMsg.split(";");
    var minosPos = {posX: parseInt(minosMsg[0], 10), posY: parseInt(minosMsg[1], 10)};
    for (i = 0; i < moves.length; i++) {
        var distanceToMinos = distance(moves[i].newX, moves[i].newY, minosPos.posX, minosPos.posY);
        if (distanceToMinos < bestDistanceToMinos) {
            bestDistanceToMinos = distanceToMinos;
        }       
    }
}
for (i = 0; i < moves.length; i++) {
    if (minosMsg !== '' && minosMsg !== undefined) {
        var distanceToMinos = distance(moves[i].newX, moves[i].newY, minosPos.posX, minosPos.posY);
        if (distanceToMinos === bestDistanceToMinos) {
            moves[i].danger += dangerValues.wayToMinos;
        }
    }
    if (closestFriend != null && distance(moves[i].x, moves[i].y, closestFriend.x, closestFriend.y) < distance(x, y, closestFriend.x, closestFriend.y)) {
        moves[i].danger += dangerValues.wayToFriend;
    }

    if (closestEnemy != null && distance(moves[i].x, moves[i].y, closestEnemy.x, closestEnemy.y) < distance(x, y, closestEnemy.x, closestEnemy.y)) {
        moves[i].danger += manyEnemies ? dangerValues.wayToManyEnemies : dangerValues.wayToEnemy;
    }
}

var bestMove = null;
var leastDanger = 10000;
for (i = 0; i < moves.length; i++) {
    if (moves[i].danger < leastDanger || (moves[i].danger === leastDanger && Math.random() < 0.5)) {
        leastDanger = moves[i].danger;
        bestMove = i;
    }
}
var newX = ("000" + moves[bestMove].newX).substr(-3, 3);
var newY = ("000" + moves[bestMove].newY).substr(-3, 3);
setMsg(newX + ";" + newY);
return bestMove;

Tries to find Minos and kill enemies. Sadly, the red team still loses, maybe because we are less players...

\$\endgroup\$
2
\$\begingroup\$

Blue Team - MiddleMan

// MiddleMan by Mwr247

// Self identification
var id = 30793;

// Bounds
var minPos = 0;
var midPos = 63;
var maxPos = 127;

// Movesets
var up = [0, 4, 2, 3, 5, 1, 6];
var down = [0, 5, 1, 6, 4, 2, 3];
var left = [0, 4, 5, 2, 1, 3, 6];
var right = [0, 3, 6, 2, 1, 4, 5];

// Our grid
var points = [0, 0, 0, 0, 0, 0, 0];

// Point system
var bound = -100000;
var death = -5000;
var dodge = 500;
var evade_best = 100;
var evade_better = 50;
var evade_good = 25;
var evade_bad = -25;
var evade_worse = -50;
var evade_worst = -100;
var kill = 4900;
var enemy = 5;

// Message
var msg = [[], []];

// Normalize values
var norm = function(val) {
    return Math.max(-1, Math.min(1, Math.round(val)));
};

// Get detailed ent data
var data = function(ent) {
    var info = {};
    info.x = ent.x - x;
    info.y = ent.y - y;
    info.normX = norm(info.x);
    info.normY = norm(info.y);
    info.distX = Math.abs(info.x);
    info.distY = Math.abs(info.y),
    info.dist = Math.sqrt(Math.pow(info.x, 2) + Math.pow(info.y, 2))
    return info
};

// Set position value
var pos = function(dir, index, val) {
    points[dir[index]] += val;
};

// Set position value above/below
var ver = function(dir, val) {
    pos(dir, 1, val);
    pos(dir, 2, val * 1.001);
    pos(dir, 3, val);
};

// Set position value on the sides
var hor = function(dir, val) {
    pos(dir, 1, val);
    pos(dir, 2, val);
};

// Vertical bound logic
if (y === minPos) {
    ver(up, bound);
} else if (y === maxPos) {
    ver(down, bound);
}

// Horizontal bound logic
if (x === minPos) {
    hor(left, bound);
} else if (x === maxPos) {
    hor(right, bound);
}

// Enemy logic
if (eNear.length) {
    var tmp;
    for (var i = 0; i < eNear.length; i++) {
        // Add the enemy to the message data
        msg[1].push([eNear[i].x, eNear[i].y]);
        tmp = data(eNear[i]);
        // We're touching, either attack or evade
        if (tmp.distY <= 1 && tmp.distX <= 1) {
            var d;
            if (tmp.distX !== 0) { // If we are not right above/below, current position is a death zone
                pos(up, 0, death);
            }
            if (tmp.distY === 0) { // Dodge like heck
                if (tmp.normX > 0) {
                    hor(right, dodge);
                    hor(left, evade_best);
                } else if (tmp.normX < 0) {
                    hor(left, dodge);
                    hor(right, evade_best);
                }
                pos(up, 2, death);
                pos(down, 2, death);
            } else { // We are above or below; finish them!
                d = tmp.y > 0 ? down : up;
                pos(d, 2 + tmp.normX, kill);
                if (tmp.normX === 0) {
                    pos(d, 1, death);
                    pos(d, 3, death);
                } else {
                    pos(d, 2, death);
                }
            }
        } else if (tmp.distY <= 2 && tmp.distX <= 2) { // We're a spot away, don't get too close!
            var d;
            if (tmp.distY === 2) { // They are two below
                d = tmp.y === 2 ? down : up;
                if (tmp.distX === 0) { // Straight down
                    pos(d, 1, death);
                    pos(d, 3, death);
                    pos(d, 2, dodge);
                    pos(d, 5, evade_good);
                    pos(d, 0, evade_best);
                } else if (tmp.distX === 1) { // One to the side
                    pos(d, 2, death);
                    pos(d, 2 + tmp.normX, dodge);
                    pos(d, 5 + tmp.normX, evade_better);
                    pos(d, 5 - tmp.normX, evade_bad);
                    pos(d, 2 - tmp.normX, evade_worst);
                } else { // Diagonals
                    pos(d, 2 + tmp.normX, death);
                    pos(d, 5 + tmp.normX, evade_better);
                    pos(d, 5 - tmp.normX, evade_bad);
                    pos(d, 2, evade_worse);
                    pos(d, 2 - tmp.normX, evade_worst);
                }
            } else { // They are to the sides
                d = tmp.normX === 1 ? right : left;
                if (tmp.distY === 0) { // Straight out
                    hor(d, death);
                    pos(d, 3, evade_better);
                    pos(d, 4, evade_better);
                } else { // A little angled
                    pos(d, 1 + (tmp.normY > 0), death);
                    pos(d, 1 + (tmp.normY < 0), evade_best);
                }
            }
        }
        
        // If there's a horizontal enemy, head that way
        if (tmp.x > 0) {
            hor(right, enemy + 16 - tmp.x);
        } else if (tmp.x < 0) {
            hor(left, enemy + 16 - tmp.x);
        }
        // If there's a vertical enemy, head that way
        if (tmp.y > 0) {
            ver(down, enemy + 16 - tmp.y);
        } else if (tmp.y < 0) {
            ver(up, enemy + 16 - tmp.y);
        }
    }
    
    // If we're near an enemy, lets try to bring them towards our friends
    if (tNear.length) {
        for (var i = 0; i < tNear.length; i++) {
            tmp = data(tNear[i]);
            if (tmp.x > 0) { // Horizontal moves
                hor(right, 1 + (16 - tmp.x) / 4);
            } else if (tmp.x < 0) {
                hor(left, 1 + (16 - tmp.x) / 4);
            }
            if (tmp.y > 0) { // Vertical moves
                ver(down, 1 + (16 - tmp.y) / 4);
            } else if (tmp.y < 0) {
                ver(up, 1 + (16 - tmp.y) / 4);
            }
        }
    }
}

// If not fighting, be the middleman you really want to be
if (y < midPos) {
    ver(down, 1);
} else if (y > midPos) {
    ver(up, 1);
}

// Hang around the horizontal middle, safe from those nasty corners
if (x < midPos) {
    hor(right, 1);
} else if (x > midPos) {
    hor(left, 1);
} else {
    pos(up, 0, 0.1);
}

// Evaluate our grid and find the winning move
var max = 0;
for (var i = 1; i < points.length; i++) {
    // If a clear winner, go with that. If there's a tie, randomize to keep things fresh
    if (points[max] < points[i] || (points[max] === points[i] && Math.round(Math.random()))) {
        max = i;
    }
}

// Set our new coordinates
var nX = x + (max === 3 || max === 6) - (max === 4 || max === 5);
var nY = y + (max === 5 || max === 1 || max === 6) - (max === 4 || max === 2 || max === 1);
msg[0] = [nX, nY];

// Set our message
setMsg(JSON.stringify(msg));

// Return the highest value move
return max;

MiddleMan is the twin brother of WallFlower (whom he replaced). Like his brother, he doesn't tend to be a social kind of guy. He'd rather just hang out in the middle of the room, watching and waiting. But that's not to say he's passive or timid. While in his spot or on his way to it, if he finds an enemy, no matter the size, he'll charge in to take them on. Knowing there's strength in numbers, he'll gladly pull them towards any teammates he finds, or alternatively, towards the center in hopes of finding others. Once his business is done, he heads back to his spot, ready for the next opportunity to help his team.

Beware, red team. While he may not seem like much, he's as fierce and persistent as they come; a master of close quarters solo fighting. He may not communicate directly with his team, but he does acknowledge them, and will work together to take down the common enemy. He has recently learned how to send messages, although he won't listen for any himself, just not his style. The format is a JSON string containing an array like this: [[selfX,selfY],[[enemy1X,enemy1Y],[enemy2X,enemy2Y]]], and so on for more enemies.

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

Blue Team - VersaBot, a polymorphic engine

My code will automatically follow the closest bot near it, providing additional firepower and protection.

// VersaBot - The PolyMorphic Companion
// Copyright 2017.5 Sam Weaver
// For this SO challenge: http://codegolf.stackexchange.com/questions/48353/red-vs-blue-pixel-team-battlebots

//FUNctions
var randInt = function(min,max) {return Math.floor((Math.random() * ((max + 1) - min)) + min);};
var degrees = function(radians) {return radians * 180 / Math.PI;};

//variables
var me = 31743;
var friendId;

if(getMsg(me) == '') {
    friendId = 0;
    setMsg(friendId);
} else {
    friendId = getMsg(me);
}

//first, check if any teammates nearby
if(tNear.length > 0) {
    //loop through and see if friend is found
    var found = false;
    var fx,fy;
    for(var index in tNear) {
        var nearAlly = tNear[index];
        //check if friend
        if(nearAlly.id == friendId) {
            //yay, let's follow 'em
            fx = nearAlly.x;
            fy = nearAlly.y;
            found = true;
            break;
        }
    }
    if(!found) {
        //pick the first friend to be a new friend
        friendId = tNear[0].id;
        fx = tNear[0].x;
        fy = tNear[0].y;
    }

    //NOW, let's follow'em
    //get the radian angle in relation to me
    var radAngle = Math.atan2(fy-y,fx-x);
    //to degrees we go!
    //console.log('friend');
    var deg = Math.floor(degrees(radAngle));
    //now reverse it so it works
    deg = -1*deg;


    //we can return the right direction now
    if(deg > 120) {
        return 4; //up left
    } else if(deg > 60) {
        return 2; //up
    } else if(deg > 0) {
        return 3; //up right
    } else if(deg < -120) {
        return 5; //down left
    } else if(deg < -60) {
        return 1; //down
    } else if(deg < 0) {
        return 6; //down right
    }
    //for some reason?
    return 0;

} else {
    //pick a random direction
    return randInt(1,6);
}

Enjoy!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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