Wormate.io is a cool game. After few minutes I was playing I thouth I could add some keyboard controls to move my own worm.
First I figured out how the game works.

After trying sniffing the traffic with Fiddler and wireshark, I realise that was such a stupid idea: I already have all the code.

js/game.js is the core in the “client” side, so actually all the game is loaded in your browser and by websockets it send your moves and the server response with all the info for the game.

So easy.

The code is ofuscade (or just minimised) but you can see immediately where are the mouse controls (around line 3308)

o.addEventListener("mousemove", function(t) {
(t = t || window.event && void 0 !== t.clientX) &&
(i.yi = Math.atan2(t.clientY - .5 * o.offsetHeight, t.clientX - .5 * o.offsetWidth))
}, !0),

it’s using one number, Math.atan2 as direction.

To load my version of the game I start a HTTP server with the modified copy of game.js

php -S localhost:5000

and tried to inject it directly from the dev console

var el = document.createElement('script');
el.src = "http://localhost:5000/game_new.js";
el.type = "text/javascript";
el.setAttribute("async","async");
document.body.appendChild(el);

but nope. Didn’t work.

After a bit of research to “how-the-hell” NOT to load one and only one file JS, I install chrome extension Sybu JS-Blocker for Chrome and everything was working fine. It blocks the game.js from the website and I load mine.

I actually substitute the

window.addEventListener("load", function() {

with

window.addEventListener("dblclick", function() {

so, with a double click on the screen the script loads.
If any have a smarter way to do that please tell me.

Let’s go back on the main hack.
The function atan2 (that I personally never use in my life) made me open the math book from the uni but after few tries everything was working great !


$("html").keydown(function(t) {
switch (t.keyCode) {
case 65: // A
i.yi = i.yi + Math.acos(0.5)
break;
case 87: // W
i.yi = -1.57
break;
case 68: // D
i.yi = i.yi - Math.acos(0.5)
break;
case 83: // S
i.yi = 1.57
break;
case 32: // Space
i.xi = !0 // this is the fast-speed mode
break;
}

At the beginning I implemented all the four directions but then it was not logic because the worm actually keep going forward, can’t go back, so… it turns only left and right!
It works. But it was impossible to play the game.

I kept digging and since I suck as a gamer, it was more logic to make the worm play alone, without my help.

The goals of the game are :
– Cath up candies, cookies and cakes!
– Kill other worms.
– Don’t die.

I spoil the end of the article : killing other worms is a bit to complicated to implement (right now).
Let’s start with the candies.

After LOT of debugging and console figured out that :
– as in JS everything is an object, all data are “stored” in one big obj that from now on I’ll call worm
worm.ol is the global time, maybe used to sync all the players
worm.xd there are big tons of interesting data
worm.xd.H is an array with the candies data !

The game is based on a circle 500 units of radius. Here we can spot x,y coordinates of the candies, the kind and the points of each.

Since I don’t need to write my best application, I just insert very dirty code into the game.js to make it works.

Just after the game.js get a message form the websocket it runs this

(I’m sorry for the stupid name if the variables and the not optimized code.)

let worm = u()
let candys = worm.xd.H // Array of all the candies around
let mx = worm.xd.G.Gd[0] // my head x coordinate
let my = worm.xd.G.Gd[1] // my head y coordinate

let min = 500
let index
let good = 1 // how good is the candy
let go_there = worm.ke.Cc.yi // my actual direction

// if it reach the limits of the circle it goes to the center
if(Math.hypot( mx,my )> 480 ){
go_there = Math.atan2( 0-my , 0-mx )
console.log("No candys..")
}
else // it finds the closest candy
{
for(c in candys){
let cx = worm.xd.H.Xe // get coordinate of the candy
let cy = worm.xd.H.Ye
let gd = worm.xd.H.Ne // points of the candy
// all the normal candies are 1 point
// the special are < 1 // and when a BIG worm die the candy have more value if(gd >= 1){
// let's find the closest one round my position
let dist = Math.hypot( cx-mx,cy-my ) // Pitagora
if (dist<min){
good = gd
min = dist
index = c
}
}
}

// the CANDIdate coordinate
cx = worm.xd.H[index].Xe
cy = worm.xd.H[index].Ye

go_there = Math.atan2( cy-my , cx-mx )

}

worm.ke.Cc.yi = go_there
worm.ke.Cc.xi = !0 // the bot will play full speed

It works great, but it doesn’t care about other worms.
I start to follow the code trying to figured out how the enemies are managed. It was not too hard :
worm.xd.I store ALL the worms in the game
worm.xd.I.ga if is true, the other worm is not too far from me and, I’ll get all the data of the enemy
worm.xd.I.Gd is a Float32Array(400) with all the coordinate of a worm Gd[0],Gd[1] is the head and so on.
worm.xd.I.ka.Va is the name of the player
worm.xd.I.ka.Rd is the team number

I scan the whole position


and if is less then min_distance I stop running and go to the opposite direction.

min = 500
let distw = 100
let go_x,go_y = 0
let where = 0
// min distance between my head and the other worm
const min_distance = 20

let enemy = worm.xd.I // worm array

for(let en in enemy){ // I run the bot in the "2 teams" mode
if(enemy[en].ga && enemy[en].ka.Rd!=myteam){ // if .ga is true the worm is nearby
let body = enemy[en].Gd // get the coordinates of the body
for(let iw = 0; iw<=400; iw = iw +2){
if(body[iw]==0){ break } // Just check if the body is empty (sth happend)

let wx = body[iw] // coordinates of each part of the body
let wy = body[iw+1]

distw = Math.hypot( wx-mx,wy-my ) // Pitagora
if(distw<min){
min = distw
go_x = wx
go_y = wy
where = iw
if(distw < min_distance){ // I stop to scan the array if it find that is already too closed
worm.ke.Cc.xi = 0 // stop running
let go_there1 = Math.atan2(go_y-my ,go_x-mx ) // first i get the straight direction to the enemy

if(go_there1 < 0 ){ // then I go to the opposite site
go_there1 = go_there1 + Math.PI
}
else{
go_there1 = go_there1 - Math.PI
} // is the rigth way to get the opposite site of Math.atan2 ? it works.
worm.ke.Cc.yi = go_there1

// I print some more info on the screen for debuggin
$("#mdata2").html("
<ul>
 	<li>mx: "+mx+"</li>
 	<li>my: "+my+"</li>
 	<li>distw: "+distw)
break ; // stop scanning the body
}
}
}
}
// if you want to know the name and the color of the closest worm
//console.log("%c Nome"+enemy[en].ka.Va+ "-&gt; "+distw,'color: '+ ((enemy[en].ka.Rd == 1 ) ? "red" : "blue") )
}

That’s all.
Not the the most clever bot ever but it plays better than me.