Initial commit
Note: Some balls don't render. This is mostly a prevent-lost-work commit.
This commit is contained in:
commit
48397199c5
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
bundle.js
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "client"]
|
||||
path = client
|
||||
url = git@github.com:pulviscriptor/agario-client.git
|
3
Makefile
Normal file
3
Makefile
Normal 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
13
README.md
Normal 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
21
app.html
Normal 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
1
client
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit c1a834d8e66193eb6bc60951dc784ea9ca20906b
|
2
loader.js
Normal file
2
loader.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
window.Client = require('./client/agario-client.js');
|
||||
window.EventEmitter = require('events').EventEmitter;
|
457
script.js
Normal file
457
script.js
Normal 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);
|
||||
};
|
Reference in a new issue