Initial commit

Note: Some balls don't render. This is mostly a prevent-lost-work commit.
This commit is contained in:
Geoffrey Frogeye 2015-06-05 16:22:41 +02:00
commit 48397199c5
9 changed files with 510 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
bundle.js

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "client"]
path = client
url = git@github.com:pulviscriptor/agario-client.git

3
Makefile Normal file
View file

@ -0,0 +1,3 @@
bundle.js: loader.js client/agario-client.js
browserify $< -o /tmp/agario-webclient-makefile -i buffer-dataview -i ws
sed -i '/var WebSocket/d' $@

13
README.md Normal file
View file

@ -0,0 +1,13 @@
#Web-browser client for [agar.io](http://agar.io)
A small browser-based graphical client for agar.io, written using [pixijs](http://www.pixijs.com/) and [agario-client](https://github.com/pulviscriptor/agario-client).
**DISCLAIMER:** This project isn't affiliated with agar.io in any way. When playing with this client, you don't get ads, which is nice for you maybe, but it doesn't pay for the servers needed to run the game.
**Note:** You won't be able to play directly on agar.io servers due to cross origin policy restrictions. You must use a proxy (you can for example use the repeater you can find in [agario-devtools](https://github.com/pulviscriptor/agario-devtools)).
##Usage
* Clone the repo with the `--recursive` option. If it was cloned without this option, run `git submodule update --init --recursive`
* Install [browserify](http://browserify.org/) globally
* Run `make`
* Open `app.html` from the browser

21
app.html Normal file
View file

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>agario-webclient</title>
<script src="http://cdnjs.cloudflare.com/ajax/libs/pixi.js/3.0.6/pixi.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/stats.js/r11/Stats.js"></script>
<script src="bundle.js"></script>
<script src="script.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
</style>
</head>
<body>
<div id="viewer">
</div>
</body>
</html>

1
client Submodule

@ -0,0 +1 @@
Subproject commit c1a834d8e66193eb6bc60951dc784ea9ca20906b

2
loader.js Normal file
View file

@ -0,0 +1,2 @@
window.Client = require('./client/agario-client.js');
window.EventEmitter = require('events').EventEmitter;

457
script.js Normal file
View file

@ -0,0 +1,457 @@
function AnimatedValue(object, name, value, timeout) {
this.overwrite(value);
this.timeout = timeout;
var _this = this;
Object.defineProperty(object, name, {
get: function() {
return _this.get();
},
set: function(x) {
return _this.set(x);
}
});
object[name + 'handler'] = this;
}
AnimatedValue.prototype = {
get: function() {
var now = performance.now(),
end = this.frTime + this.timeout;
if (now >= end) {
return this.toVal;
} else {
return this.toVal - (this.toVal - this.frVal) * (end - now) / this.timeout;
}
},
set: function(value) {
if (value != this.toVal) {
this.frVal = this.get();
this.toVal = value;
this.frTime = performance.now();
}
},
overwrite: function(value) {
this.frVal = value;
this.toVal = value;
this.frTime = performance.now() - this.timeout; // so end == now
}
};
function BallView(main, ball) {
this.main = main;
this.ball = ball;
this.graphic = new PIXI.Graphics();
new AnimatedValue(this, 'x', 0, 100);
new AnimatedValue(this, 'y', 0, 100);
new AnimatedValue(this, 's', 0, 100);
var _this = this;
// console.log(_this.ball.id, 'NEW');
this.appear();
this.ball.on('appear', function() {
_this.appear();
});
this.ball.on('destroy', function() {
// console.log(_this.ball.id, 'DES');
_this.main.stage.removeChild(_this.graphic);
});
this.ball.on('disappear', function() {
// console.log(_this.ball.id, 'DIS');
_this.main.stage.removeChild(_this.graphic);
});
this.ball.on('move', function() {
_this.x = _this.ball.x;
_this.y = _this.ball.y;
});
this.ball.on('resize', function() {
_this.s = _this.ball.size;
});
}
BallView.prototype = {
appear: function() {
// console.log(this.ball.id, 'APP');
this.x = this.ball.x;
this.y = this.ball.y;
this.s = this.ball.size;
this.shape();
this.main.stage.addChild(this.graphic);
},
shape: function() {
this.graphic.clear();
this.graphic.beginFill(this.ball.color.replace('#', '0x'),
this.ball.virus ? 0.5 : 0.9);
this.graphic.drawCircle(0, 0, 1);
this.graphic.endFill();
},
render: function() {
this.graphic.position.x = this.x;
this.graphic.position.y = this.y;
this.graphic.scale.x = this.graphic.scale.y = this.s;
}
};
function Viewer(client, container) {
this.client = client;
this.container = container;
this.balls = {};
this.addRenderer();
this.addStats();
var _this = this;
client.once('mapSizeLoad', function(min_x, min_y, max_x, max_y) {
_this.gameWidth = max_x;
_this.gameHeight = max_y;
_this.initStage();
_this.addListners();
_this.addBorders();
_this.animate();
_this.emit('launched');
});
window.addEventListener('resize', function() {
_this.updateSize();
});
}
Viewer.prototype = {
getSize: function() {
this.width = window.innerWidth;
this.height = window.innerHeight;
},
addRenderer: function() {
this.getSize();
this.renderer = PIXI.autoDetectRenderer(this.width, this.height, {
antialias: true
});
this.container.appendChild(this.renderer.view);
},
updateSize: function() {
this.getSize();
this.renderer.resize(this.width, this.height);
},
initStage: function() {
this.stage = new PIXI.Container();
this.cam = {};
new AnimatedValue(this.cam, 'x', this.gameWidth / 2, 100);
new AnimatedValue(this.cam, 'y', this.gameHeight / 2, 100);
new AnimatedValue(this.cam, 's', 0.5 / 2, 100);
},
addListners: function() {
var _this = this;
this.client.on('ballAppear', function(id) {
if (!_this.balls[id]) {
_this.balls[id] = new BallView(_this, this.balls[id]);
}
});
this.client.on('ballDestroy', function(id) {
delete this.balls[id];
});
},
addBorders: function() {
this.borders = new PIXI.Graphics();
this.borders.lineStyle(5, 0xFF3300, 1);
this.borders.drawRect(0, 0, this.gameWidth, this.gameHeight);
this.stage.addChild(this.borders);
},
addStats: function() {
this.stats = new Stats();
this.stats.setMode(1);
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.left = '0px';
this.stats.domElement.style.top = '0px';
document.body.appendChild(this.stats.domElement);
},
posCamera: function() {
var x = y = p = 0;
for (var ball_id in this.client.my_balls) {
var ball = this.client.balls[this.client.my_balls[ball_id]];
if (!ball.visible) continue;
x += ball.x * ball.size;
y += ball.y * ball.size;
p += ball.size;
}
if (p > 0) { // if we have visible ball(s)
this.cam.x = x / p;
this.cam.y = y / p;
this.cam.s = 0.04 * this.width / p; // Scale
// TODO Better scale calculation
} // else: don't move the camera
},
render: function() {
for (var ball_id in this.client.balls) {
var ball = this.balls[ball_id];
if (ball) {
ball.render();
}
}
},
animate: function() {
this.stats.begin();
this.render();
this.posCamera();
this.stage.scale.x = this.stage.scale.y = this.cam.s;
this.stage.position.x = -this.cam.x * this.stage.scale.x + this.width / 2;
this.stage.position.y = -this.cam.y * this.stage.scale.y + this.height / 2;
this.renderer.render(this.stage);
this.stats.end();
this.emit('animate');
var _this = this;
requestAnimationFrame(function() {
_this.animate();
});
}
};
// Inherit from EventEmitter
for (var key in EventEmitter.prototype) {
Viewer.prototype[key] = EventEmitter.prototype[key];
}
function Pointer(viewer) {
this.viewer = viewer;
this.client = this.viewer.client;
this.dest = { // Destination, relative to camera center
x: 0,
y: 0
};
var _this = this;
this.viewer.once('launched', function() {
_this.viewer.stage.interactive = true;
_this.viewer.stage.on('mousemove', function(e) {
_this.pointermove(e);
});
_this.viewer.stage.on('touchmove', function(e) {
_this.pointermove(e);
});
_this.viewer.on('animate', function(e) {
_this.move();
});
});
window.addEventListener('keydown', function(e) {
if (e.keyCode == 87) {
_this.client.eject();
} else if (e.keyCode == 32) {
_this.client.split();
}
});
}
Pointer.prototype = {
move: function() {
this.client.moveTo(this.viewer.cam.x + this.dest.x, this.viewer.cam.y + this.dest.y);
},
pointermove: function(e) {
var gamePos = e.data.getLocalPosition(this.viewer.stage);
this.dest = { // TODO deadzone
x: gamePos.x - this.viewer.cam.x,
y: gamePos.y - this.viewer.cam.y
};
this.move();
}
};
function Controller(client) {
this.client = client;
this.server = {
region: 'EU-London',
ip: '127.0.0.1',
port: 9158
}
this.nick = 'agario-client';
this.autoRespawn = false;
this.gui = new dat.GUI();
this.servgui = this.gui.addFolder('Server');
this.servgui.add(this.server, 'region', ['US-Fremont', 'US-Atlanta', 'BR-Brazil', 'EU-London', 'RU-Russia', 'JP-Tokyo', 'CN-China', 'SG-Singapore', 'TK-Turkey']);
this.servgui.add(this, 'findServer');
this.servgui.add(this.server, 'ip');
this.servgui.add(this.server, 'port');
this.servgui.add(this, 'connect');
this.servgui.add(this, 'disconnect');
this.servgui.open();
this.cellgui = this.gui.addFolder('Cell');
this.cellgui.add(this, 'nick');
this.cellgui.add(this, 'spawn');
this.cellgui.add(this, 'autoRespawn');
this.leadergui = this.gui.addFolder('Leaderboard');
this.leaders = {};
this.resetLeader();
for (var i = 1; i <= 10; i++) {
this.leadergui.add(this.leaders, i);
}
var _this = this;
client.on('connected', function() {
_this.servgui.close();
_this.cellgui.open();
_this.leadergui.open();
if (_this.autoRespawn) {
_this.spawn();
}
});
client.on('reset', function() {
_this.servgui.open();
_this.cellgui.close();
_this.leadergui.close();
_this.resetLeader();
});
client.on('lostMyBalls', function() {
if (_this.autoRespawn) {
_this.spawn();
}
});
client.on('leaderBoardUpdate', function(old, leaders) {
for (var i in leaders) {
var rank = parseInt(i) + 1;
_this.leaders[rank] = this.balls[leaders[i]].name || 'An unnamed cell';
for (var j in _this.leadergui.__controllers) {
_this.leadergui.__controllers[j].updateDisplay();
}
}
});
}
Controller.prototype = {
findServer: function() {
// Because of SOP, this will never work
x = new XMLHttpRequest();
x.open('POST', 'http://m.agar.io', false);
x.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
x.setRequestHeader('Content-Length', this.server.region.length);
// x.setRequestHeader('Origin', 'http://agar.io');
// x.setRequestHeader('Referer', 'http://agar.io/');
x.send(this.server.region);
s = x.responseText.split(':');
this.server.ip = s[0];
this.server.port = s[1];
for (var i in this.servgui.__controllers) {
this.servgui.__controllers[i].updateDisplay();
}
},
connect: function() {
this.client.connect('ws://' + this.server.ip + ':' + this.server.port);
},
disconnect: function() {
this.client.disconnect();
},
spawn: function() {
this.client.spawn(this.nick);
},
resetLeader: function() {
for (var i = 1; i <= 10; i++) {
this.leaders[i] = '---';
}
}
};
function IA(client) {
this.client = client;
this.begin();
}
IA.prototype = {
begin: function() {
var _this = this;
this.interval = setInterval(function() {
_this.food();
// _this.decide();
}, 100);
},
end: function() {
clearInterval(this.interval_id);
},
getDistanceBetweenBalls: function(ball_1, ball_2) { //this calculates distance between 2 balls
return Math.sqrt(Math.pow(ball_1.x - ball_2.x, 2) + Math.pow(ball_2.y - ball_1.y, 2));
},
getAngleBetweenBalls: function(b1, b2) { // output in rad
dX = b2.x - b1.x;
dY = b2.y - b1.y;
return Math.tan(dY / dX);
},
food: function() {
var candidate_ball = null; //first we don't have candidate to eat
var candidate_distance = 0;
var my_ball = this.client.balls[this.client.my_balls[0]]; //we get our first ball. We don't care if there more then one, its just example.
if (!my_ball) return; //if our ball not spawned yet then we abort. We will come back here in 100ms later
for (var ball_id in this.client.balls) { //we go true all balls we know about
var ball = this.client.balls[ball_id];
if (ball.virus) continue; //if ball is a virus (green non edible thing) then we skip it
if (!ball.visible) continue; //if ball is not on our screen (field of view) then we skip it
if (ball.mine) continue; //if ball is our ball - then we skip it
if (ball.size / my_ball.size > 0.5) continue; //if ball is bigger than 50% of our size - then we skip it
var distance = this.getDistanceBetweenBalls(ball, my_ball); //we calculate distances between our ball and candidate
if (candidate_ball && distance > candidate_distance) continue; //if we do have some candidate and distance to it smaller, than distance to this ball, we skip it
candidate_ball = ball; //we found new candidate and we record him
candidate_distance = this.getDistanceBetweenBalls(ball, my_ball); //we record distance to him to compare it with other balls
}
if (!candidate_ball) return; //if we didn't find any candidate, we abort. We will come back here in 100ms later
this.client.log('closest ' + candidate_ball + ', distance ' + candidate_distance);
this.client.moveTo(candidate_ball.x, candidate_ball.y); //we send move command to move to food's coordinates
},
decide: function() {
var my_ball = this.client.balls[this.client.my_balls[0]]; // TODO Handle more balls
if (!my_ball) return;
var candidates = [];
for (var ball_id in this.client.balls) {
var ball = this.client.balls[ball_id];
if (!ball.visible) continue;
var score = 0;
if (ball.mine) {
score = 1;
} else {
if (ball.virus) {
if (ball.size > my_ball.size) {
score = -5;
} else {
score = 5;
}
} else {
score = Math.max(1000 - this.getDistanceBetweenBalls(ball, my_ball) - ball.size - my_ball.size, 0);
if (ball.size < my_ball.size) {
score = score;
} else {
score = -score * 5;
}
}
}
candidates.push({
x: ball.x - my_ball.x,
y: ball.y - my_ball.y,
score: score
});
}
var x = y = p = 0;
for (var candidate_id in candidates) {
var candidate = candidates[candidate_id];
// console.log(candidate.x, candidate.y, candidate.score)
x += candidate.x * candidate.score;
y += candidate.y * candidate.score;
p += candidate.score;
}
this.client.moveTo(my_ball.x + x / p, my_ball.y + y / p);
}
};
var d = {}; // DEBUG Allow access from console
window.onload = function() {
d.client = new Client('worker');
d.viewer = new Viewer(d.client, document.getElementById('viewer'));
d.controller = new Controller(d.client);
d.pointer = new Pointer(d.viewer);
// d.ia = new IA(d.client);
};

9
style.css Normal file
View file

@ -0,0 +1,9 @@
body,
#viewer,
canvas {
margin: 0;
}
body {
overflow: hidden;
}