Skip to content

Instantly share code, notes, and snippets.

@gaudat
Created July 26, 2016 08:24
Show Gist options
  • Save gaudat/8ccf040ecad7b58fe51171e5f077a677 to your computer and use it in GitHub Desktop.
Save gaudat/8ccf040ecad7b58fe51171e5f077a677 to your computer and use it in GitHub Desktop.
Schlongs of Slither
/*
The MIT License (MIT)
Copyright (c) 2016 Jesse Miller <[email protected]>
Copyright (c) 2016 Alexey Korepanov <[email protected]>
Copyright (c) 2016 Ermiya Eskandary & Théophile Cailliau and other contributors
https://jmiller.mit-license.org/
*/
// ==UserScript==
// @name Mybot with 100K to circle (was Name Slither.io Bot Championship Edition)
// @namespace https://github.com/j-c-m/Slither.io-bot
// @version 3.0.5.1
// @description Slither.io Bot Championship Edition
// @author Jesse Miller
// @match http://slither.io/
// @supportURL https://github.com/j-c-m/Slither.io-bot/issues
// @grant none
// ==/UserScript==
// Custom logging function - disabled by default
window.log = function () {
if (window.logDebugging) {
console.log.apply(console, arguments);
}
};
var canvas = window.canvas = (function (window) {
return {
// Spoofs moving the mouse to the provided coordinates.
setMouseCoordinates: function (point) {
window.xm = point.x;
window.ym = point.y;
},
// Convert map coordinates to mouse coordinates.
mapToMouse: function (point) {
var mouseX = (point.x - window.snake.xx) * window.gsc;
var mouseY = (point.y - window.snake.yy) * window.gsc;
return { x: mouseX, y: mouseY };
},
// Map cordinates to Canvas cordinate shortcut
mapToCanvas: function (point) {
var c = {
x: window.mww2 + (point.x - window.view_xx) * window.gsc,
y: window.mhh2 + (point.y - window.view_yy) * window.gsc
};
return c;
},
// Map to Canvas coordinate conversion for drawing circles.
// Radius also needs to scale by .gsc
circleMapToCanvas: function (circle) {
var newCircle = canvas.mapToCanvas({
x: circle.x,
y: circle.y
});
return canvas.circle(
newCircle.x,
newCircle.y,
circle.radius * window.gsc
);
},
// Constructor for point type
point: function (x, y) {
var p = {
x: Math.round(x),
y: Math.round(y)
};
return p;
},
// Constructor for rect type
rect: function (x, y, w, h) {
var r = {
x: Math.round(x),
y: Math.round(y),
width: Math.round(w),
height: Math.round(h)
};
return r;
},
// Constructor for circle type
circle: function (x, y, r) {
var c = {
x: Math.round(x),
y: Math.round(y),
radius: Math.round(r)
};
return c;
},
// Fast atan2
fastAtan2: function (y, x) {
const QPI = Math.PI / 4;
const TQPI = 3 * Math.PI / 4;
var r = 0.0;
var angle = 0.0;
var abs_y = Math.abs(y) + 1e-10;
if (x < 0) {
r = (x + abs_y) / (abs_y - x);
angle = TQPI;
} else {
r = (x - abs_y) / (x + abs_y);
angle = QPI;
}
angle += (0.1963 * r * r - 0.9817) * r;
if (y < 0) {
return -angle;
}
return angle;
},
// Adjusts zoom in response to the mouse wheel.
setZoom: function (e) {
// Scaling ratio
if (window.gsc) {
window.gsc *= Math.pow(0.9, e.wheelDelta / -120 || e.detail / 2 || 0);
window.desired_gsc = window.gsc;
}
},
// Restores zoom to the default value.
resetZoom: function () {
window.gsc = 0.9;
window.desired_gsc = 0.9;
},
// Maintains Zoom
maintainZoom: function () {
if (window.desired_gsc !== undefined) {
window.gsc = window.desired_gsc;
}
},
// Sets background to the given image URL.
// Defaults to slither.io's own background.
setBackground: function (url) {
url = typeof url !== 'undefined' ? url : '/s/bg45.jpg';
window.ii.src = url;
},
// Draw a rectangle on the canvas.
drawRect: function (rect, color, fill, alpha) {
if (alpha === undefined) alpha = 1;
var context = window.mc.getContext('2d');
var lc = canvas.mapToCanvas({ x: rect.x, y: rect.y });
context.save();
context.globalAlpha = alpha;
context.strokeStyle = color;
context.rect(lc.x, lc.y, rect.width * window.gsc, rect.height * window.gsc);
context.stroke();
if (fill) {
context.fillStyle = color;
context.fill();
}
context.restore();
},
// Draw a circle on the canvas.
drawCircle: function (circle, color, fill, alpha) {
if (alpha === undefined) alpha = 1;
if (circle.radius === undefined) circle.radius = 5;
var context = window.mc.getContext('2d');
var drawCircle = canvas.circleMapToCanvas(circle);
context.save();
context.globalAlpha = alpha;
context.beginPath();
context.strokeStyle = color;
context.arc(drawCircle.x, drawCircle.y, drawCircle.radius, 0, Math.PI * 2);
context.stroke();
if (fill) {
context.fillStyle = color;
context.fill();
}
context.restore();
},
// Draw an angle.
// @param {number} start -- where to start the angle
// @param {number} angle -- width of the angle
// @param {bool} danger -- green if false, red if true
drawAngle: function (start, angle, color, fill, alpha) {
if (alpha === undefined) alpha = 0.6;
var context = window.mc.getContext('2d');
context.save();
context.globalAlpha = alpha;
context.beginPath();
context.moveTo(window.mc.width / 2, window.mc.height / 2);
context.arc(window.mc.width / 2, window.mc.height / 2, window.gsc * 100, start, angle);
context.lineTo(window.mc.width / 2, window.mc.height / 2);
context.closePath();
context.stroke();
if (fill) {
context.fillStyle = color;
context.fill();
}
context.restore();
},
// Draw a line on the canvas.
drawLine: function (p1, p2, color, width) {
if (width === undefined) width = 5;
var context = window.mc.getContext('2d');
var dp1 = canvas.mapToCanvas(p1);
var dp2 = canvas.mapToCanvas(p2);
context.save();
context.beginPath();
context.lineWidth = width * window.gsc;
context.strokeStyle = color;
context.moveTo(dp1.x, dp1.y);
context.lineTo(dp2.x, dp2.y);
context.stroke();
context.restore();
},
// Given the start and end of a line, is point left.
isLeft: function (start, end, point) {
return ((end.x - start.x) * (point.y - start.y) -
(end.y - start.y) * (point.x - start.x)) > 0;
},
// Get distance squared
getDistance2: function (x1, y1, x2, y2) {
var distance2 = Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2);
return distance2;
},
getDistance2FromSnake: function (point) {
point.distance = canvas.getDistance2(window.snake.xx, window.snake.yy,
point.xx, point.yy);
return point;
},
// return unit vector in the direction of the argument
unitVector: function (v) {
var l = Math.sqrt(v.x * v.x + v.y * v.y);
if (l > 0) {
return {
x: v.x / l,
y: v.y / l
};
} else {
return {
x: 0,
y: 0
};
}
},
// Check if point in Rect
pointInRect: function (point, rect) {
if (rect.x <= point.x && rect.y <= point.y &&
rect.x + rect.width >= point.x && rect.y + rect.height >= point.y) {
return true;
}
return false;
},
// check if point is in polygon
pointInPoly: function (point, poly) {
if (point.x < poly.minx || point.x > poly.maxx ||
point.y < poly.miny || point.y > poly.maxy) {
return false;
}
let c = false;
const l = poly.pts.length;
for (let i = 0, j = l - 1; i < l; j = i++) {
if ( ((poly.pts[i].y > point.y) != (poly.pts[j].y > point.y)) &&
(point.x < (poly.pts[j].x - poly.pts[i].x) * (point.y - poly.pts[i].y) /
(poly.pts[j].y - poly.pts[i].y) + poly.pts[i].x) ) {
c = !c;
}
}
return c;
},
addPolyBox: function (poly) {
var minx = poly.pts[0].x;
var maxx = poly.pts[0].x;
var miny = poly.pts[0].y;
var maxy = poly.pts[0].y;
for (let p = 1, l = poly.pts.length; p < l; p++) {
if (poly.pts[p].x < minx) {
minx = poly.pts[p].x;
}
if (poly.pts[p].x > maxx) {
maxx = poly.pts[p].x;
}
if (poly.pts[p].y < miny) {
miny = poly.pts[p].y;
}
if (poly.pts[p].y > maxy) {
maxy = poly.pts[p].y;
}
}
return {
pts: poly.pts,
minx: minx,
maxx: maxx,
miny: miny,
maxy: maxy
};
},
cross: function (o, a, b) {
return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
},
convexHullSort: function (a, b) {
return a.x == b.x ? a.y - b.y : a.x - b.x;
},
convexHull: function (points) {
points.sort(canvas.convexHullSort);
var lower = [];
for (let i = 0, l = points.length; i < l; i++) {
while (lower.length >= 2 && canvas.cross(
lower[lower.length - 2], lower[lower.length - 1], points[i]) <= 0) {
lower.pop();
}
lower.push(points[i]);
}
var upper = [];
for (let i = points.length - 1; i >= 0; i--) {
while (upper.length >= 2 && canvas.cross(
upper[upper.length - 2], upper[upper.length - 1], points[i]) <= 0) {
upper.pop();
}
upper.push(points[i]);
}
upper.pop();
lower.pop();
return lower.concat(upper);
},
// Check if circles intersect
circleIntersect: function (circle1, circle2) {
var bothRadii = circle1.radius + circle2.radius;
var point = {};
// Pretends the circles are squares for a quick collision check.
// If it collides, do the more expensive circle check.
if (circle1.x + bothRadii > circle2.x &&
circle1.y + bothRadii > circle2.y &&
circle1.x < circle2.x + bothRadii &&
circle1.y < circle2.y + bothRadii) {
var distance2 = canvas.getDistance2(circle1.x, circle1.y, circle2.x, circle2.y);
if (distance2 < bothRadii * bothRadii) {
point = {
x: ((circle1.x * circle2.radius) + (circle2.x * circle1.radius)) /
bothRadii,
y: ((circle1.y * circle2.radius) + (circle2.y * circle1.radius)) /
bothRadii,
ang: 0.0
};
point.ang = canvas.fastAtan2(
point.y - window.snake.yy, point.x - window.snake.xx);
if (window.visualDebugging) {
var collisionPointCircle = canvas.circle(
point.x,
point.y,
5
);
canvas.drawCircle(circle2, '#ff9900', false);
canvas.drawCircle(collisionPointCircle, '#66ff66', true);
}
return point;
}
}
return false;
}
};
})(window);
var bot = window.bot = (function (window) {
return {
isBotRunning: false,
isBotEnabled: true,
stage: 'grow',
collisionPoints: [],
collisionAngles: [],
foodAngles: [],
scores: [],
foodTimeout: undefined,
sectorBoxSide: 0,
defaultAccel: 0,
sectorBox: {},
currentFood: {},
opt: {
// target fps
targetFps: 20,
// size of arc for collisionAngles
arcSize: Math.PI / 8,
// radius multiple for circle intersects
radiusMult: 10,
// food cluster size to trigger acceleration
foodAccelSz: 200,
// maximum angle of food to trigger acceleration
foodAccelDa: Math.PI / 2,
// how many frames per action
actionFrames: 2,
// how many frames to delay action after collision
collisionDelay: 10,
// base speed
speedBase: 5.78,
// front angle size
frontAngle: Math.PI / 2,
// percent of angles covered by same snake to be considered an encircle attempt
enCircleThreshold: 0.5625,
// percent of angles covered by all snakes to move to safety
enCircleAllThreshold: 0.5625,
// distance multiplier for enCircleAllThreshold
enCircleDistanceMult: 20,
// snake score to start circling on self
followCircleLength: 100000,
// direction for followCircle: +1 for counter clockwise and -1 for clockwise
followCircleDirection: +1
},
MID_X: 0,
MID_Y: 0,
MAP_R: 0,
MAXARC: 0,
getSnakeWidth: function (sc) {
if (sc === undefined) sc = window.snake.sc;
return Math.round(sc * 29.0);
},
quickRespawn: function () {
window.dead_mtm = 0;
window.login_fr = 0;
bot.isBotRunning = false;
window.forcing = true;
bot.connect();
window.forcing = false;
},
connect: function () {
if (window.force_ip && window.force_port) {
window.forceServer(window.force_ip, window.force_port);
}
window.connect();
},
// angleBetween - get the smallest angle between two angles (0-pi)
angleBetween: function (a1, a2) {
var r1 = 0.0;
var r2 = 0.0;
r1 = (a1 - a2) % Math.PI;
r2 = (a2 - a1) % Math.PI;
return r1 < r2 ? -r1 : r2;
},
// Change heading to ang
changeHeadingAbs: function (angle) {
var cos = Math.cos(angle);
var sin = Math.sin(angle);
window.goalCoordinates = {
x: Math.round(
window.snake.xx + (bot.headCircle.radius) * cos),
y: Math.round(
window.snake.yy + (bot.headCircle.radius) * sin)
};
/*if (window.visualDebugging) {
canvas.drawLine({
x: window.snake.xx,
y: window.snake.yy},
window.goalCoordinates, 'yellow', '8');
}*/
canvas.setMouseCoordinates(canvas.mapToMouse(window.goalCoordinates));
},
// Change heading by ang
// +0-pi turn left
// -0-pi turn right
changeHeadingRel: function (angle) {
var heading = {
x: window.snake.xx + 500 * bot.cos,
y: window.snake.yy + 500 * bot.sin
};
var cos = Math.cos(-angle);
var sin = Math.sin(-angle);
window.goalCoordinates = {
x: Math.round(
cos * (heading.x - window.snake.xx) -
sin * (heading.y - window.snake.yy) + window.snake.xx),
y: Math.round(
sin * (heading.x - window.snake.xx) +
cos * (heading.y - window.snake.yy) + window.snake.yy)
};
canvas.setMouseCoordinates(canvas.mapToMouse(window.goalCoordinates));
},
// Change heading to the best angle for avoidance.
headingBestAngle: function () {
var best;
var distance;
var openAngles = [];
var openStart;
var sIndex = bot.getAngleIndex(window.snake.ehang) + bot.MAXARC / 2;
if (sIndex > bot.MAXARC) sIndex -= bot.MAXARC;
for (var i = 0; i < bot.MAXARC; i++) {
if (bot.collisionAngles[i] === undefined) {
distance = 0;
if (openStart === undefined) openStart = i;
} else {
distance = bot.collisionAngles[i].distance;
if (openStart) {
openAngles.push({
openStart: openStart,
openEnd: i - 1,
sz: (i - 1) - openStart
});
openStart = undefined;
}
}
if (best === undefined ||
(best.distance < distance && best.distance !== 0)) {
best = {
distance: distance,
aIndex: i
};
}
}
if (openStart && openAngles[0]) {
openAngles[0].openStart = openStart;
openAngles[0].sz = openAngles[0].openEnd - openStart;
if (openAngles[0].sz < 0) openAngles[0].sz += bot.MAXARC;
} else if (openStart) {
openAngles.push({openStart: openStart, openEnd: openStart, sz: 0});
}
if (openAngles.length > 0) {
openAngles.sort(bot.sortSz);
bot.changeHeadingAbs(
(openAngles[0].openEnd - openAngles[0].sz / 2) * bot.opt.arcSize);
} else {
bot.changeHeadingAbs(best.aIndex * bot.opt.arcSize);
}
},
// Avoid collision point by ang
// ang radians <= Math.PI (180deg)
avoidCollisionPoint: function (point, ang) {
if (ang === undefined || ang > Math.PI) {
ang = Math.PI;
}
var end = {
x: window.snake.xx + 2000 * bot.cos,
y: window.snake.yy + 2000 * bot.sin
};
if (window.visualDebugging) {
canvas.drawLine(
{ x: window.snake.xx, y: window.snake.yy },
end,
'orange', 5);
canvas.drawLine(
{ x: window.snake.xx, y: window.snake.yy },
{ x: point.x, y: point.y },
'red', 5);
}
if (canvas.isLeft(
{ x: window.snake.xx, y: window.snake.yy }, end,
{ x: point.x, y: point.y })) {
bot.changeHeadingAbs(point.ang - ang);
} else {
bot.changeHeadingAbs(point.ang + ang);
}
},
// get collision angle index, expects angle +/i 0 to Math.PI
getAngleIndex: function (angle) {
var index;
if (angle < 0) {
angle += 2 * Math.PI;
}
index = Math.round(angle * (1 / bot.opt.arcSize));
if (index === bot.MAXARC) {
return 0;
}
return index;
},
// Add to collisionAngles if distance is closer
addCollisionAngle: function (sp) {
var ang = canvas.fastAtan2(
Math.round(sp.yy - window.snake.yy),
Math.round(sp.xx - window.snake.xx));
var aIndex = bot.getAngleIndex(ang);
var actualDistance = Math.round(Math.pow(
Math.sqrt(sp.distance) - sp.radius, 2));
if (bot.collisionAngles[aIndex] === undefined ||
bot.collisionAngles[aIndex].distance > sp.distance) {
bot.collisionAngles[aIndex] = {
x: Math.round(sp.xx),
y: Math.round(sp.yy),
ang: ang,
snake: sp.snake,
distance: actualDistance,
radius: sp.radius,
aIndex: aIndex
};
}
},
// Add and score foodAngles
addFoodAngle: function (f) {
var ang = canvas.fastAtan2(
Math.round(f.yy - window.snake.yy),
Math.round(f.xx - window.snake.xx));
var aIndex = bot.getAngleIndex(ang);
canvas.getDistance2FromSnake(f);
if (bot.collisionAngles[aIndex] === undefined ||
Math.sqrt(bot.collisionAngles[aIndex].distance) >
Math.sqrt(f.distance) + bot.snakeRadius * bot.opt.radiusMult * bot.speedMult / 2) {
if (bot.foodAngles[aIndex] === undefined) {
bot.foodAngles[aIndex] = {
x: Math.round(f.xx),
y: Math.round(f.yy),
ang: ang,
da: Math.abs(bot.angleBetween(ang, window.snake.ehang)),
distance: f.distance,
sz: f.sz,
score: Math.pow(f.sz, 2) / f.distance
};
} else {
bot.foodAngles[aIndex].sz += Math.round(f.sz);
bot.foodAngles[aIndex].score += Math.pow(f.sz, 2) / f.distance;
if (bot.foodAngles[aIndex].distance > f.distance) {
bot.foodAngles[aIndex].x = Math.round(f.xx);
bot.foodAngles[aIndex].y = Math.round(f.yy);
bot.foodAngles[aIndex].distance = f.distance;
}
}
}
},
// Get closest collision point per snake.
getCollisionPoints: function () {
var scPoint;
bot.collisionPoints = [];
bot.collisionAngles = [];
for (var snake = 0, ls = window.snakes.length; snake < ls; snake++) {
scPoint = undefined;
if (window.snakes[snake].id !== window.snake.id &&
window.snakes[snake].alive_amt === 1) {
var s = window.snakes[snake];
var sRadius = bot.getSnakeWidth(s.sc) / 2;
var sSpMult = Math.min(1, s.sp / 5.78 - 1 );
scPoint = {
xx: s.xx + Math.cos(s.ehang) * sRadius * sSpMult * bot.opt.radiusMult / 2,
yy: s.yy + Math.sin(s.ehang) * sRadius * sSpMult * bot.opt.radiusMult / 2,
snake: snake,
radius: bot.headCircle.radius,
head: true
};
canvas.getDistance2FromSnake(scPoint);
bot.addCollisionAngle(scPoint);
bot.collisionPoints.push(scPoint);
if (window.visualDebugging) {
canvas.drawCircle(canvas.circle(
scPoint.xx,
scPoint.yy,
scPoint.radius),
'red', false);
}
scPoint = undefined;
for (var pts = 0, lp = s.pts.length; pts < lp; pts++) {
if (!s.pts[pts].dying &&
canvas.pointInRect(
{
x: s.pts[pts].xx,
y: s.pts[pts].yy
}, bot.sectorBox)
) {
var collisionPoint = {
xx: s.pts[pts].xx,
yy: s.pts[pts].yy,
snake: snake,
radius: sRadius
};
if (window.visualDebugging && true === false) {
canvas.drawCircle(canvas.circle(
collisionPoint.xx,
collisionPoint.yy,
collisionPoint.radius),
'#00FF00', false);
}
canvas.getDistance2FromSnake(collisionPoint);
bot.addCollisionAngle(collisionPoint);
if (collisionPoint.distance <= Math.pow(
(bot.headCircle.radius)
+ collisionPoint.radius, 2)) {
bot.collisionPoints.push(collisionPoint);
if (window.visualDebugging) {
canvas.drawCircle(canvas.circle(
collisionPoint.xx,
collisionPoint.yy,
collisionPoint.radius
), 'red', false);
}
}
}
}
}
}
// WALL
if (canvas.getDistance2(bot.MID_X, bot.MID_Y, window.snake.xx, window.snake.yy) >
Math.pow(bot.MAP_R - 1000, 2)) {
var midAng = canvas.fastAtan2(
window.snake.yy - bot.MID_X, window.snake.xx - bot.MID_Y);
scPoint = {
xx: bot.MID_X + bot.MAP_R * Math.cos(midAng),
yy: bot.MID_Y + bot.MAP_R * Math.sin(midAng),
snake: -1,
radius: bot.snakeWidth
};
canvas.getDistance2FromSnake(scPoint);
bot.collisionPoints.push(scPoint);
bot.addCollisionAngle(scPoint);
if (window.visualDebugging) {
canvas.drawCircle(canvas.circle(
scPoint.xx,
scPoint.yy,
scPoint.radius
), 'yellow', false);
}
}
bot.collisionPoints.sort(bot.sortDistance);
if (window.visualDebugging) {
for (var i = 0; i < bot.collisionAngles.length; i++) {
if (bot.collisionAngles[i] !== undefined) {
canvas.drawLine(
{ x: window.snake.xx, y: window.snake.yy },
{ x: bot.collisionAngles[i].x, y: bot.collisionAngles[i].y },
'red', 2);
}
}
}
},
// Is collisionPoint (xx) in frontAngle
inFrontAngle: function (point) {
var ang = canvas.fastAtan2(
Math.round(point.y - window.snake.yy),
Math.round(point.x - window.snake.xx));
if (Math.abs(bot.angleBetween(ang, window.snake.ehang)) < bot.opt.frontAngle) {
return true;
} else {
return false;
}
},
// Checks to see if you are going to collide with anything in the collision detection radius
checkCollision: function () {
var point;
bot.getCollisionPoints();
if (bot.collisionPoints.length === 0) return false;
for (var i = 0; i < bot.collisionPoints.length; i++) {
var collisionCircle = canvas.circle(
bot.collisionPoints[i].xx,
bot.collisionPoints[i].yy,
bot.collisionPoints[i].radius
);
// -1 snake is special case for non snake object.
if ((point = canvas.circleIntersect(bot.headCircle, collisionCircle)) &&
bot.inFrontAngle(point)) {
if (bot.collisionPoints[i].snake !== -1 &&
bot.collisionPoints[i].head &&
window.snakes[bot.collisionPoints[i].snake].sp > 10) {
window.setAcceleration(1);
} else {
window.setAcceleration(bot.defaultAccel);
}
bot.avoidCollisionPoint(point);
return true;
}
}
window.setAcceleration(bot.defaultAccel);
return false;
},
checkEncircle: function () {
var enSnake = [];
var high = 0;
var highSnake;
var enAll = 0;
for (var i = 0; i < bot.collisionAngles.length; i++) {
if (bot.collisionAngles[i] !== undefined) {
var s = bot.collisionAngles[i].snake;
if (enSnake[s]) {
enSnake[s]++;
} else {
enSnake[s] = 1;
}
if (enSnake[s] > high) {
high = enSnake[s];
highSnake = s;
}
if (bot.collisionAngles[i].distance <
Math.pow(bot.snakeRadius * bot.opt.enCircleDistanceMult, 2)) {
enAll++;
}
}
}
if (high > bot.MAXARC * bot.opt.enCircleThreshold) {
bot.headingBestAngle();
if (high !== bot.MAXARC && window.snakes[highSnake].sp > 10) {
window.setAcceleration(1);
} else {
window.setAcceleration(bot.defaultAccel);
}
if (window.visualDebugging) {
canvas.drawCircle(canvas.circle(
window.snake.xx,
window.snake.yy,
bot.opt.radiusMult * bot.snakeRadius),
'red', true, 0.2);
}
return true;
}
if (enAll > bot.MAXARC * bot.opt.enCircleAllThreshold) {
bot.headingBestAngle();
window.setAcceleration(bot.defaultAccel);
if (window.visualDebugging) {
canvas.drawCircle(canvas.circle(
window.snake.xx,
window.snake.yy,
bot.snakeRadius * bot.opt.enCircleDistanceMult),
'yellow', true, 0.2);
}
return true;
} else {
if (window.visualDebugging) {
canvas.drawCircle(canvas.circle(
window.snake.xx,
window.snake.yy,
bot.snakeRadius * bot.opt.enCircleDistanceMult),
'yellow');
}
}
window.setAcceleration(bot.defaultAccel);
return false;
},
populatePts: function () {
let x = window.snake.xx + window.snake.fx;
let y = window.snake.yy + window.snake.fy;
let l = 0.0;
bot.pts = [{
x: x,
y: y,
len: l
}];
for (let p = window.snake.pts.length - 1; p >= 0; p--) {
if (window.snake.pts[p].dying) {
continue;
} else {
let xx = window.snake.pts[p].xx + window.snake.pts[p].fx;
let yy = window.snake.pts[p].yy + window.snake.pts[p].fy;
let ll = l + Math.sqrt(canvas.getDistance2(x, y, xx, yy));
bot.pts.push({
x: xx,
y: yy,
len: ll
});
x = xx;
y = yy;
l = ll;
}
}
bot.len = l;
},
// set the direction of rotation based on the velocity of
// the head with respect to the center of mass
determineCircleDirection: function () {
// find center mass (cx, cy)
let cx = 0.0;
let cy = 0.0;
let pn = bot.pts.length;
for (let p = 0; p < pn; p++) {
cx += bot.pts[p].x;
cy += bot.pts[p].y;
}
cx /= pn;
cy /= pn;
// vector from (cx, cy) to the head
let head = {
x: window.snake.xx + window.snake.fx,
y: window.snake.yy + window.snake.fy
};
let dx = head.x - cx;
let dy = head.y - cy;
// check the sign of dot product of (bot.cos, bot.sin) and (-dy, dx)
if (- dy * bot.cos + dx * bot.sin > 0) {
// clockwise
bot.opt.followCircleDirection = -1;
} else {
// couter clockwise
bot.opt.followCircleDirection = +1;
}
},
// returns a point on snake's body on given length from the head
// assumes that bot.pts is populated
smoothPoint: function (t) {
// range check
if (t >= bot.len) {
let tail = bot.pts[bot.pts.length - 1];
return {
x: tail.x,
y: tail.y
};
} else if (t <= 0 ) {
return {
x: bot.pts[0].x,
y: bot.pts[0].y
};
}
// binary search
let p = 0;
let q = bot.pts.length - 1;
while (q - p > 1) {
let m = Math.round((p + q) / 2);
if (t > bot.pts[m].len) {
p = m;
} else {
q = m;
}
}
// now q = p + 1, and the point is in between;
// compute approximation
let wp = bot.pts[q].len - t;
let wq = t - bot.pts[p].len;
let w = wp + wq;
return {
x: (wp * bot.pts[p].x + wq * bot.pts[q].x) / w,
y: (wp * bot.pts[p].y + wq * bot.pts[q].y) / w
};
},
// finds a point on snake's body closest to the head;
// returns length from the head
// excludes points close to the head
closestBodyPoint: function () {
let head = {
x: window.snake.xx + window.snake.fx,
y: window.snake.yy + window.snake.fy
};
let ptsLength = bot.pts.length;
// skip head area
let start_n = 0;
let start_d2 = 0.0;
for ( ;; ) {
let prev_d2 = start_d2;
start_n ++;
start_d2 = canvas.getDistance2(head.x, head.y,
bot.pts[start_n].x, bot.pts[start_n].y);
if (start_d2 < prev_d2 || start_n == ptsLength - 1) {
break;
}
}
if (start_n >= ptsLength || start_n <= 1) {
return bot.len;
}
// find closets point in bot.pts
let min_n = start_n;
let min_d2 = start_d2;
for (let n = min_n + 1; n < ptsLength; n++) {
let d2 = canvas.getDistance2(head.x, head.y, bot.pts[n].x, bot.pts[n].y);
if (d2 < min_d2) {
min_n = n;
min_d2 = d2;
}
}
// find second closest point
let next_n = min_n;
let next_d2 = min_d2;
if (min_n == ptsLength - 1) {
next_n = min_n - 1;
next_d2 = canvas.getDistance2(head.x, head.y,
bot.pts[next_n].x, bot.pts[next_n].y);
} else {
let d2m = canvas.getDistance2(head.x, head.y,
bot.pts[min_n - 1].x, bot.pts[min_n - 1].y);
let d2p = canvas.getDistance2(head.x, head.y,
bot.pts[min_n + 1].x, bot.pts[min_n + 1].y);
if (d2m < d2p) {
next_n = min_n - 1;
next_d2 = d2m;
} else {
next_n = min_n + 1;
next_d2 = d2p;
}
}
// compute approximation
let t2 = bot.pts[min_n].len - bot.pts[next_n].len;
t2 *= t2;
if (t2 == 0) {
return bot.pts[min_n].len;
} else {
let min_w = t2 - (min_d2 - next_d2);
let next_w = t2 + (min_d2 - next_d2);
return (bot.pts[min_n].len * min_w + bot.pts[next_n].len * next_w) / (2 * t2);
}
},
bodyDangerZone: function (
offset, targetPoint, targetPointNormal, closePointDist, pastTargetPoint, closePoint) {
var head = {
x: window.snake.xx + window.snake.fx,
y: window.snake.yy + window.snake.fy
};
const o = bot.opt.followCircleDirection;
var pts = [
{
x: head.x - o * offset * bot.sin,
y: head.y + o * offset * bot.cos
},
{
x: head.x + bot.snakeWidth * bot.cos +
offset * (bot.cos - o * bot.sin),
y: head.y + bot.snakeWidth * bot.sin +
offset * (bot.sin + o * bot.cos)
},
{
x: head.x + 1.75 * bot.snakeWidth * bot.cos +
o * 0.3 * bot.snakeWidth * bot.sin +
offset * (bot.cos - o * bot.sin),
y: head.y + 1.75 * bot.snakeWidth * bot.sin -
o * 0.3 * bot.snakeWidth * bot.cos +
offset * (bot.sin + o * bot.cos)
},
{
x: head.x + 2.5 * bot.snakeWidth * bot.cos +
o * 0.7 * bot.snakeWidth * bot.sin +
offset * (bot.cos - o * bot.sin),
y: head.y + 2.5 * bot.snakeWidth * bot.sin -
o * 0.7 * bot.snakeWidth * bot.cos +
offset * (bot.sin + o * bot.cos)
},
{
x: head.x + 3 * bot.snakeWidth * bot.cos +
o * 1.2 * bot.snakeWidth * bot.sin +
offset * bot.cos,
y: head.y + 3 * bot.snakeWidth * bot.sin -
o * 1.2 * bot.snakeWidth * bot.cos +
offset * bot.sin
},
{
x: targetPoint.x +
targetPointNormal.x * (offset + 0.5 * Math.max(closePointDist, 0)),
y: targetPoint.y +
targetPointNormal.y * (offset + 0.5 * Math.max(closePointDist, 0))
},
{
x: pastTargetPoint.x + targetPointNormal.x * offset,
y: pastTargetPoint.y + targetPointNormal.y * offset
},
pastTargetPoint,
targetPoint,
closePoint
];
pts = canvas.convexHull(pts);
var poly = {
pts: pts
};
poly = canvas.addPolyBox(poly);
return (poly);
},
followCircleSelf: function () {
bot.populatePts();
bot.determineCircleDirection();
const o = bot.opt.followCircleDirection;
// exit if too short
if (bot.len < 9 * bot.snakeWidth) {
return;
}
var head = {
x: window.snake.xx + window.snake.fx,
y: window.snake.yy + window.snake.fy
};
let closePointT = bot.closestBodyPoint();
let closePoint = bot.smoothPoint(closePointT);
// approx tangent and normal vectors and closePoint
var closePointNext = bot.smoothPoint(closePointT - bot.snakeWidth);
var closePointTangent = canvas.unitVector({
x: closePointNext.x - closePoint.x,
y: closePointNext.y - closePoint.y});
var closePointNormal = {
x: - o * closePointTangent.y,
y: o * closePointTangent.x
};
// angle wrt closePointTangent
var currentCourse = Math.asin(Math.max(
-1, Math.min(1, bot.cos * closePointNormal.x + bot.sin * closePointNormal.y)));
// compute (oriented) distance from the body at closePointDist
var closePointDist = (head.x - closePoint.x) * closePointNormal.x +
(head.y - closePoint.y) * closePointNormal.y;
// construct polygon for snake inside
var insidePolygonStartT = 5 * bot.snakeWidth;
var insidePolygonEndT = closePointT + 5 * bot.snakeWidth;
var insidePolygonPts = [
bot.smoothPoint(insidePolygonEndT),
bot.smoothPoint(insidePolygonStartT)
];
for (let t = insidePolygonStartT; t < insidePolygonEndT; t += bot.snakeWidth) {
insidePolygonPts.push(bot.smoothPoint(t));
}
var insidePolygon = canvas.addPolyBox({
pts: insidePolygonPts
});
// get target point; this is an estimate where we land if we hurry
var targetPointT = closePointT;
var targetPointFar = 0.0;
let targetPointStep = bot.snakeWidth / 64;
for (let h = closePointDist, a = currentCourse; h >= 0.125 * bot.snakeWidth; ) {
targetPointT -= targetPointStep;
targetPointFar += targetPointStep * Math.cos(a);
h += targetPointStep * Math.sin(a);
a = Math.max(-Math.PI / 4, a - targetPointStep / bot.snakeWidth);
}
var targetPoint = bot.smoothPoint(targetPointT);
var pastTargetPointT = targetPointT - 3 * bot.snakeWidth;
var pastTargetPoint = bot.smoothPoint(pastTargetPointT);
// look for danger from enemies
var enemyBodyOffsetDelta = 0.25 * bot.snakeWidth;
var enemyHeadDist2 = 64 * 64 * bot.snakeWidth * bot.snakeWidth;
for (let snake = 0, snakesNum = window.snakes.length; snake < snakesNum; snake++) {
if (window.snakes[snake].id !== window.snake.id
&& window.snakes[snake].alive_amt === 1) {
let enemyHead = {
x: window.snakes[snake].xx + window.snakes[snake].fx,
y: window.snakes[snake].yy + window.snakes[snake].fy
};
let enemyAhead = {
x: enemyHead.x +
Math.cos(window.snakes[snake].ang) * bot.snakeWidth,
y: enemyHead.y +
Math.sin(window.snakes[snake].ang) * bot.snakeWidth
};
// heads
if (!canvas.pointInPoly(enemyHead, insidePolygon)) {
enemyHeadDist2 = Math.min(
enemyHeadDist2,
canvas.getDistance2(enemyHead.x, enemyHead.y,
targetPoint.x, targetPoint.y),
canvas.getDistance2(enemyAhead.x, enemyAhead.y,
targetPoint.x, targetPoint.y)
);
}
// bodies
let offsetSet = false;
let offset = 0.0;
let cpolbody = {};
for (let pts = 0, ptsNum = window.snakes[snake].pts.length;
pts < ptsNum; pts++) {
if (!window.snakes[snake].pts[pts].dying) {
let point = {
x: window.snakes[snake].pts[pts].xx +
window.snakes[snake].pts[pts].fx,
y: window.snakes[snake].pts[pts].yy +
window.snakes[snake].pts[pts].fy
};
while (!offsetSet || (enemyBodyOffsetDelta >= -bot.snakeWidth
&& canvas.pointInPoly(point, cpolbody))) {
if (!offsetSet) {
offsetSet = true;
} else {
enemyBodyOffsetDelta -= 0.0625 * bot.snakeWidth;
}
offset = 0.5 * (bot.snakeWidth +
bot.getSnakeWidth(window.snakes[snake].sc)) +
enemyBodyOffsetDelta;
cpolbody = bot.bodyDangerZone(
offset, targetPoint, closePointNormal, closePointDist,
pastTargetPoint, closePoint);
}
}
}
}
}
var enemyHeadDist = Math.sqrt(enemyHeadDist2);
// plot inside polygon
if (window.visualDebugging) {
for (let p = 0, l = insidePolygon.pts.length; p < l; p++) {
let q = p + 1;
if (q == l) {
q = 0;
}
canvas.drawLine(
{x: insidePolygon.pts[p].x, y: insidePolygon.pts[p].y},
{x: insidePolygon.pts[q].x, y: insidePolygon.pts[q].y},
'orange');
}
}
// mark closePoint
if (window.visualDebugging) {
canvas.drawCircle(canvas.circle(
closePoint.x,
closePoint.y,
bot.snakeWidth * 0.25
), 'white', false);
}
// mark safeZone
if (window.visualDebugging) {
canvas.drawCircle(canvas.circle(
targetPoint.x,
targetPoint.y,
bot.snakeWidth + 2 * targetPointFar
), 'white', false);
canvas.drawCircle(canvas.circle(
targetPoint.x,
targetPoint.y,
0.2 * bot.snakeWidth
), 'white', false);
}
// draw sample cpolbody
if (window.visualDebugging) {
let soffset = 0.5 * bot.snakeWidth;
let scpolbody = bot.bodyDangerZone(
soffset, targetPoint, closePointNormal,
closePointDist, pastTargetPoint, closePoint);
for (let p = 0, l = scpolbody.pts.length; p < l; p++) {
let q = p + 1;
if (q == l) {
q = 0;
}
canvas.drawLine(
{x: scpolbody.pts[p].x, y: scpolbody.pts[p].y},
{x: scpolbody.pts[q].x, y: scpolbody.pts[q].y},
'white');
}
}
// TAKE ACTION
// expand?
let targetCourse = currentCourse + 0.25;
// enemy head nearby?
let headProx = -1.0 - (2 * targetPointFar - enemyHeadDist) / bot.snakeWidth;
if (headProx > 0) {
headProx = 0.125 * headProx * headProx;
} else {
headProx = - 0.5 * headProx * headProx;
}
targetCourse = Math.min(targetCourse, headProx);
// enemy body nearby?
targetCourse = Math.min(
targetCourse, targetCourse + (enemyBodyOffsetDelta - 0.0625 * bot.snakeWidth) /
bot.snakeWidth);
// small tail?
var tailBehind = bot.len - closePointT;
var targetDir = canvas.unitVector({
x: bot.opt.followCircleTarget.x - head.x,
y: bot.opt.followCircleTarget.y - head.y
});
var driftQ = targetDir.x * closePointNormal.x + targetDir.y * closePointNormal.y;
var allowTail = bot.snakeWidth * (2 - 0.5 * driftQ);
// a line in the direction of the target point
if (window.visualDebugging) {
canvas.drawLine(
{ x: head.x, y: head.y },
{ x: head.x + allowTail * targetDir.x, y: head.y + allowTail * targetDir.y },
'red');
}
targetCourse = Math.min(
targetCourse,
(tailBehind - allowTail + (bot.snakeWidth - closePointDist)) /
bot.snakeWidth);
// far away?
targetCourse = Math.min(
targetCourse, - 0.5 * (closePointDist - 4 * bot.snakeWidth) / bot.snakeWidth);
// final corrections
// too fast in?
targetCourse = Math.max(targetCourse, -0.75 * closePointDist / bot.snakeWidth);
// too fast out?
targetCourse = Math.min(targetCourse, 1.0);
var goalDir = {
x: closePointTangent.x * Math.cos(targetCourse) -
o * closePointTangent.y * Math.sin(targetCourse),
y: closePointTangent.y * Math.cos(targetCourse) +
o * closePointTangent.x * Math.sin(targetCourse)
};
var goal = {
x: head.x + goalDir.x * 4 * bot.snakeWidth,
y: head.y + goalDir.y * 4 * bot.snakeWidth
};
if (window.goalCoordinates
&& Math.abs(goal.x - window.goalCoordinates.x) < 1000
&& Math.abs(goal.y - window.goalCoordinates.y) < 1000) {
window.goalCoordinates = {
x: Math.round(goal.x * 0.25 + window.goalCoordinates.x * 0.75),
y: Math.round(goal.y * 0.25 + window.goalCoordinates.y * 0.75)
};
} else {
window.goalCoordinates = {
x: Math.round(goal.x),
y: Math.round(goal.y)
};
}
canvas.setMouseCoordinates(canvas.mapToMouse(window.goalCoordinates));
},
// Sorting by property 'score' descending
sortScore: function (a, b) {
return b.score - a.score;
},
// Sorting by property 'sz' descending
sortSz: function (a, b) {
return b.sz - a.sz;
},
// Sorting by property 'distance' ascending
sortDistance: function (a, b) {
return a.distance - b.distance;
},
computeFoodGoal: function () {
bot.foodAngles = [];
for (var i = 0; i < window.foods.length && window.foods[i] !== null; i++) {
var f = window.foods[i];
if (!f.eaten &&
!(
canvas.circleIntersect(
canvas.circle(f.xx, f.yy, 2),
bot.sidecircle_l) ||
canvas.circleIntersect(
canvas.circle(f.xx, f.yy, 2),
bot.sidecircle_r))) {
bot.addFoodAngle(f);
}
}
bot.foodAngles.sort(bot.sortScore);
if (bot.foodAngles[0] !== undefined && bot.foodAngles[0].sz > 0) {
bot.currentFood = { x: bot.foodAngles[0].x,
y: bot.foodAngles[0].y,
sz: bot.foodAngles[0].sz,
da: bot.foodAngles[0].da };
} else {
bot.currentFood = { x: bot.MID_X, y: bot.MID_Y, sz: 0 };
}
},
foodAccel: function () {
var aIndex = 0;
if (bot.currentFood && bot.currentFood.sz > bot.opt.foodAccelSz) {
aIndex = bot.getAngleIndex(bot.currentFood.ang);
if (
bot.collisionAngles[aIndex] && bot.collisionAngles[aIndex].distance >
bot.currentFood.distance + bot.snakeRadius * bot.opt.radiusMult
&& bot.currentFood.da < bot.opt.foodAccelDa) {
return 1;
}
if (bot.collisionAngles[aIndex] === undefined
&& bot.currentFood.da < bot.opt.foodAccelDa) {
return 1;
}
}
return bot.defaultAccel;
},
toCircle: function () {
for (var i = 0; i < window.snake.pts.length && window.snake.pts[i].dying; i++);
const o = bot.opt.followCircleDirection;
var tailCircle = canvas.circle(
window.snake.pts[i].xx,
window.snake.pts[i].yy,
bot.headCircle.radius
);
if (window.visualDebugging) {
canvas.drawCircle(tailCircle, 'blue', false);
}
window.setAcceleration(bot.defaultAccel);
bot.changeHeadingRel(o * Math.PI / 32);
if (canvas.circleIntersect(bot.headCircle, tailCircle)) {
bot.stage = 'circle';
}
},
every: function () {
bot.MID_X = window.grd;
bot.MID_Y = window.grd;
bot.MAP_R = window.grd * 0.98;
bot.MAXARC = (2 * Math.PI) / bot.opt.arcSize;
if (bot.opt.followCircleTarget === undefined) {
bot.opt.followCircleTarget = {
x: bot.MID_X,
y: bot.MID_Y
};
}
bot.sectorBoxSide = Math.floor(Math.sqrt(window.sectors.length)) * window.sector_size;
bot.sectorBox = canvas.rect(
window.snake.xx - (bot.sectorBoxSide / 2),
window.snake.yy - (bot.sectorBoxSide / 2),
bot.sectorBoxSide, bot.sectorBoxSide);
// if (window.visualDebugging) canvas.drawRect(bot.sectorBox, '#c0c0c0', true, 0.1);
bot.cos = Math.cos(window.snake.ang);
bot.sin = Math.sin(window.snake.ang);
bot.speedMult = window.snake.sp / bot.opt.speedBase;
bot.snakeRadius = bot.getSnakeWidth() / 2;
bot.snakeWidth = bot.getSnakeWidth();
bot.snakeLength = Math.floor(15 * (window.fpsls[window.snake.sct] + window.snake.fam /
window.fmlts[window.snake.sct] - 1) - 5);
bot.headCircle = canvas.circle(
window.snake.xx + bot.cos * Math.min(1, bot.speedMult - 1) *
bot.opt.radiusMult / 2 * bot.snakeRadius,
window.snake.yy + bot.sin * Math.min(1, bot.speedMult - 1) *
bot.opt.radiusMult / 2 * bot.snakeRadius,
bot.opt.radiusMult / 2 * bot.snakeRadius
);
if (window.visualDebugging) {
canvas.drawCircle(bot.headCircle, 'blue', false);
}
bot.sidecircle_r = canvas.circle(
window.snake.lnp.xx -
((window.snake.lnp.yy + bot.sin * bot.snakeWidth) -
window.snake.lnp.yy),
window.snake.lnp.yy +
((window.snake.lnp.xx + bot.cos * bot.snakeWidth) -
window.snake.lnp.xx),
bot.snakeWidth * bot.speedMult
);
bot.sidecircle_l = canvas.circle(
window.snake.lnp.xx +
((window.snake.lnp.yy + bot.sin * bot.snakeWidth) -
window.snake.lnp.yy),
window.snake.lnp.yy -
((window.snake.lnp.xx + bot.cos * bot.snakeWidth) -
window.snake.lnp.xx),
bot.snakeWidth * bot.speedMult
);
},
// Main bot
go: function () {
bot.every();
if (bot.snakeLength < bot.opt.followCircleLength) {
bot.stage = 'grow';
}
if (bot.currentFood && bot.stage !== 'grow') {
bot.currentFood = undefined;
}
if (bot.stage === 'circle') {
window.setAcceleration(bot.defaultAccel);
bot.followCircleSelf();
} else if (bot.checkCollision() || bot.checkEncircle()) {
if (bot.actionTimeout) {
window.clearTimeout(bot.actionTimeout);
bot.actionTimeout = window.setTimeout(
bot.actionTimer, 1000 / bot.opt.targetFps * bot.opt.collisionDelay);
}
} else {
if (bot.snakeLength > bot.opt.followCircleLength) {
bot.stage = 'tocircle';
}
if (bot.actionTimeout === undefined) {
bot.actionTimeout = window.setTimeout(
bot.actionTimer, 1000 / bot.opt.targetFps * bot.opt.actionFrames);
}
window.setAcceleration(bot.foodAccel());
}
},
// Timer version of food check
actionTimer: function () {
if (window.playing && window.snake !== null && window.snake.alive_amt === 1) {
if (bot.stage === 'grow') {
bot.computeFoodGoal();
window.goalCoordinates = bot.currentFood;
canvas.setMouseCoordinates(canvas.mapToMouse(window.goalCoordinates));
} else if (bot.stage === 'tocircle') {
bot.toCircle();
}
}
bot.actionTimeout = undefined;
}
};
})(window);
var userInterface = window.userInterface = (function (window, document) {
// Save the original slither.io functions so we can modify them, or reenable them later.
var original_keydown = document.onkeydown;
var original_onmouseDown = window.onmousedown;
var original_oef = window.oef;
var original_redraw = window.redraw;
var original_onmousemove = window.onmousemove;
window.oef = function () { };
window.redraw = function () { };
return {
overlays: {},
gfxEnabled: true,
initServerIp: function () {
var parent = document.getElementById('playh');
var serverDiv = document.createElement('div');
var serverIn = document.createElement('input');
serverDiv.style.width = '244px';
serverDiv.style.margin = '-30px auto';
serverDiv.style.boxShadow = 'rgb(0, 0, 0) 0px 6px 50px';
serverDiv.style.opacity = 1;
serverDiv.style.background = 'rgb(76, 68, 124)';
serverDiv.className = 'taho';
serverDiv.style.display = 'block';
serverIn.className = 'sumsginp';
serverIn.placeholder = '0.0.0.0:444';
serverIn.maxLength = 21;
serverIn.style.width = '220px';
serverIn.style.height = '24px';
serverDiv.appendChild(serverIn);
parent.appendChild(serverDiv);
userInterface.server = serverIn;
},
initOverlays: function () {
var botOverlay = document.createElement('div');
botOverlay.style.position = 'fixed';
botOverlay.style.right = '5px';
botOverlay.style.bottom = '112px';
botOverlay.style.width = '150px';
botOverlay.style.height = '85px';
// botOverlay.style.background = 'rgba(0, 0, 0, 0.5)';
botOverlay.style.color = '#C0C0C0';
botOverlay.style.fontFamily = 'Consolas, Verdana';
botOverlay.style.zIndex = 999;
botOverlay.style.fontSize = '14px';
botOverlay.style.padding = '5px';
botOverlay.style.borderRadius = '5px';
botOverlay.className = 'nsi';
document.body.appendChild(botOverlay);
var serverOverlay = document.createElement('div');
serverOverlay.style.position = 'fixed';
serverOverlay.style.right = '5px';
serverOverlay.style.bottom = '5px';
serverOverlay.style.width = '160px';
serverOverlay.style.height = '14px';
serverOverlay.style.color = '#C0C0C0';
serverOverlay.style.fontFamily = 'Consolas, Verdana';
serverOverlay.style.zIndex = 999;
serverOverlay.style.fontSize = '14px';
serverOverlay.className = 'nsi';
document.body.appendChild(serverOverlay);
var prefOverlay = document.createElement('div');
prefOverlay.style.position = 'fixed';
prefOverlay.style.left = '10px';
prefOverlay.style.top = '75px';
prefOverlay.style.width = '260px';
prefOverlay.style.height = '210px';
// prefOverlay.style.background = 'rgba(0, 0, 0, 0.5)';
prefOverlay.style.color = '#C0C0C0';
prefOverlay.style.fontFamily = 'Consolas, Verdana';
prefOverlay.style.zIndex = 999;
prefOverlay.style.fontSize = '14px';
prefOverlay.style.padding = '5px';
prefOverlay.style.borderRadius = '5px';
prefOverlay.className = 'nsi';
document.body.appendChild(prefOverlay);
var statsOverlay = document.createElement('div');
statsOverlay.style.position = 'fixed';
statsOverlay.style.left = '10px';
statsOverlay.style.top = '295px';
statsOverlay.style.width = '140px';
statsOverlay.style.height = '210px';
// statsOverlay.style.background = 'rgba(0, 0, 0, 0.5)';
statsOverlay.style.color = '#C0C0C0';
statsOverlay.style.fontFamily = 'Consolas, Verdana';
statsOverlay.style.zIndex = 998;
statsOverlay.style.fontSize = '14px';
statsOverlay.style.padding = '5px';
statsOverlay.style.borderRadius = '5px';
statsOverlay.className = 'nsi';
document.body.appendChild(statsOverlay);
userInterface.overlays.botOverlay = botOverlay;
userInterface.overlays.serverOverlay = serverOverlay;
userInterface.overlays.prefOverlay = prefOverlay;
userInterface.overlays.statsOverlay = statsOverlay;
},
toggleOverlays: function () {
Object.keys(userInterface.overlays).forEach(function (okey) {
var oVis = userInterface.overlays[okey].style.visibility !== 'hidden' ?
'hidden' : 'visible';
userInterface.overlays[okey].style.visibility = oVis;
window.visualDebugging = oVis === 'visible';
});
},
toggleGfx: function () {
if (userInterface.gfxEnabled) {
var c = window.mc.getContext('2d');
c.save();
c.fillStyle = "#000000",
c.fillRect(0, 0, window.mww, window.mhh),
c.restore();
var d = document.createElement('div');
d.style.position = 'fixed';
d.style.top = '50%';
d.style.left = '50%';
d.style.width = '200px';
d.style.height = '60px';
d.style.color = '#C0C0C0';
d.style.fontFamily = 'Consolas, Verdana';
d.style.zIndex = 999;
d.style.margin = '-30px 0 0 -100px';
d.style.fontSize = '20px';
d.style.textAlign = 'center';
d.className = 'nsi';
document.body.appendChild(d);
userInterface.gfxOverlay = d;
window.lbf.innerHTML = '';
} else {
document.body.removeChild(userInterface.gfxOverlay);
userInterface.gfxOverlay = undefined;
}
userInterface.gfxEnabled = !userInterface.gfxEnabled;
},
// Save variable to local storage
savePreference: function (item, value) {
window.localStorage.setItem(item, value);
userInterface.onPrefChange();
},
// Load a variable from local storage
loadPreference: function (preference, defaultVar) {
var savedItem = window.localStorage.getItem(preference);
if (savedItem !== null) {
if (savedItem === 'true') {
window[preference] = true;
} else if (savedItem === 'false') {
window[preference] = false;
} else {
window[preference] = savedItem;
}
window.log('Setting found for ' + preference + ': ' + window[preference]);
} else {
window[preference] = defaultVar;
window.log('No setting found for ' + preference +
'. Used default: ' + window[preference]);
}
userInterface.onPrefChange();
return window[preference];
},
// Saves username when you click on "Play" button
playButtonClickListener: function () {
userInterface.saveNick();
userInterface.loadPreference('autoRespawn', false);
userInterface.onPrefChange();
if (userInterface.server.value) {
let s = userInterface.server.value.split(':');
if (s.length === 2) {
window.force_ip = s[0];
window.force_port = s[1];
bot.connect();
}
} else {
window.force_ip = undefined;
window.force_port = undefined;
}
},
// Preserve nickname
saveNick: function () {
var nick = document.getElementById('nick').value;
userInterface.savePreference('savedNick', nick);
},
// Hide top score
hideTop: function () {
var nsidivs = document.querySelectorAll('div.nsi');
for (var i = 0; i < nsidivs.length; i++) {
if (nsidivs[i].style.top === '4px' && nsidivs[i].style.width === '300px') {
nsidivs[i].style.visibility = 'hidden';
bot.isTopHidden = true;
window.topscore = nsidivs[i];
}
}
},
// Store FPS data
framesPerSecond: {
fps: 0,
fpsTimer: function () {
if (window.playing && window.fps && window.lrd_mtm) {
if (Date.now() - window.lrd_mtm > 970) {
userInterface.framesPerSecond.fps = window.fps;
}
}
}
},
onkeydown: function (e) {
// Original slither.io onkeydown function + whatever is under it
original_keydown(e);
if (window.playing) {
// Letter `T` to toggle bot
if (e.keyCode === 84) {
bot.isBotEnabled = !bot.isBotEnabled;
}
// Letter 'U' to toggle debugging (console)
if (e.keyCode === 85) {
window.logDebugging = !window.logDebugging;
console.log('Log debugging set to: ' + window.logDebugging);
userInterface.savePreference('logDebugging', window.logDebugging);
}
// Letter 'Y' to toggle debugging (visual)
if (e.keyCode === 89) {
window.visualDebugging = !window.visualDebugging;
console.log('Visual debugging set to: ' + window.visualDebugging);
userInterface.savePreference('visualDebugging', window.visualDebugging);
}
// Letter 'I' to toggle autorespawn
if (e.keyCode === 73) {
window.autoRespawn = !window.autoRespawn;
console.log('Automatic Respawning set to: ' + window.autoRespawn);
userInterface.savePreference('autoRespawn', window.autoRespawn);
}
// Letter 'H' to toggle hidden mode
if (e.keyCode === 72) {
userInterface.toggleOverlays();
}
// Letter 'G' to toggle graphics
if (e.keyCode === 71) {
userInterface.toggleGfx();
}
// Letter 'O' to change rendermode (visual)
if (e.keyCode === 79) {
userInterface.toggleMobileRendering(!window.mobileRender);
}
// Letter 'A' to increase collision detection radius
if (e.keyCode === 65) {
bot.opt.radiusMult++;
console.log(
'radiusMult set to: ' + bot.opt.radiusMult);
}
// Letter 'S' to decrease collision detection radius
if (e.keyCode === 83) {
if (bot.opt.radiusMult > 1) {
bot.opt.radiusMult--;
console.log(
'radiusMult set to: ' +
bot.opt.radiusMult);
}
}
// Letter 'Z' to reset zoom
if (e.keyCode === 90) {
canvas.resetZoom();
}
// Letter 'Q' to quit to main menu
if (e.keyCode === 81) {
window.autoRespawn = false;
userInterface.quit();
}
// 'ESC' to quickly respawn
if (e.keyCode === 27) {
bot.quickRespawn();
}
userInterface.onPrefChange();
}
},
onmousedown: function (e) {
if (window.playing) {
switch (e.which) {
// "Left click" to manually speed up the slither
case 1:
bot.defaultAccel = 1;
if (!bot.isBotEnabled) {
original_onmouseDown(e);
}
break;
// "Right click" to toggle bot in addition to the letter "T"
case 3:
bot.isBotEnabled = !bot.isBotEnabled;
break;
}
} else {
original_onmouseDown(e);
}
userInterface.onPrefChange();
},
onmouseup: function () {
bot.defaultAccel = 0;
},
// Manual mobile rendering
toggleMobileRendering: function (mobileRendering) {
window.mobileRender = mobileRendering;
window.log('Mobile rendering set to: ' + window.mobileRender);
userInterface.savePreference('mobileRender', window.mobileRender);
// Set render mode
if (window.mobileRender) {
window.render_mode = 1;
window.want_quality = 0;
window.high_quality = false;
} else {
window.render_mode = 2;
window.want_quality = 1;
window.high_quality = true;
}
},
// Update stats overlay.
updateStats: function () {
var oContent = [];
var median;
if (bot.scores.length === 0) return;
median = Math.round((bot.scores[Math.floor((bot.scores.length - 1) / 2)] +
bot.scores[Math.ceil((bot.scores.length - 1) / 2)]) / 2);
oContent.push('games played: ' + bot.scores.length);
oContent.push('a: ' + Math.round(
bot.scores.reduce(function (a, b) { return a + b; }) / (bot.scores.length)) +
' m: ' + median);
for (var i = 0; i < bot.scores.length && i < 10; i++) {
oContent.push(i + 1 + '. ' + bot.scores[i]);
}
userInterface.overlays.statsOverlay.innerHTML = oContent.join('<br/>');
},
onPrefChange: function () {
// Set static display options here.
var oContent = [];
var ht = userInterface.handleTextColor;
oContent.push('version: ' + GM_info.script.version);
oContent.push('[T] bot: ' + ht(bot.isBotEnabled));
oContent.push('[O] mobile rendering: ' + ht(window.mobileRender));
oContent.push('[A/S] radius multiplier: ' + bot.opt.radiusMult);
oContent.push('[I] auto respawn: ' + ht(window.autoRespawn));
oContent.push('[Y] visual debugging: ' + ht(window.visualDebugging));
oContent.push('[U] log debugging: ' + ht(window.logDebugging));
oContent.push('[Mouse Wheel] zoom');
oContent.push('[Z] reset zoom');
oContent.push('[ESC] quick respawn');
oContent.push('[Q] quit to menu');
userInterface.overlays.prefOverlay.innerHTML = oContent.join('<br/>');
},
onFrameUpdate: function () {
// Botstatus overlay
if (window.playing && window.snake !== null) {
let oContent = [];
oContent.push('fps: ' + userInterface.framesPerSecond.fps);
// Display the X and Y of the snake
oContent.push('x: ' +
(Math.round(window.snake.xx) || 0) + ' y: ' +
(Math.round(window.snake.yy) || 0));
if (window.goalCoordinates) {
oContent.push('target');
oContent.push('x: ' + window.goalCoordinates.x + ' y: ' +
window.goalCoordinates.y);
if (window.goalCoordinates.sz) {
oContent.push('sz: ' + window.goalCoordinates.sz);
}
}
userInterface.overlays.botOverlay.innerHTML = oContent.join('<br/>');
if (userInterface.gfxOverlay) {
let gContent = [];
gContent.push('<b>' + window.snake.nk + '</b>');
gContent.push(bot.snakeLength);
gContent.push('[' + window.rank + '/' + window.snake_count + ']');
userInterface.gfxOverlay.innerHTML = gContent.join('<br/>');
}
if (window.bso !== undefined && userInterface.overlays.serverOverlay.innerHTML !==
window.bso.ip + ':' + window.bso.po) {
userInterface.overlays.serverOverlay.innerHTML =
window.bso.ip + ':' + window.bso.po;
}
}
if (window.playing && window.visualDebugging) {
// Only draw the goal when a bot has a goal.
if (window.goalCoordinates && bot.isBotEnabled) {
var headCoord = { x: window.snake.xx, y: window.snake.yy };
canvas.drawLine(
headCoord,
window.goalCoordinates,
'green');
canvas.drawCircle(window.goalCoordinates, 'red', true);
}
}
},
oefTimer: function () {
var start = Date.now();
canvas.maintainZoom();
original_oef();
if (userInterface.gfxEnabled) {
original_redraw();
} else {
window.visualDebugging = false;
}
if (window.playing && bot.isBotEnabled && window.snake !== null) {
window.onmousemove = function () { };
bot.isBotRunning = true;
bot.go();
} else if (bot.isBotEnabled && bot.isBotRunning) {
bot.isBotRunning = false;
if (window.lastscore && window.lastscore.childNodes[1]) {
bot.scores.push(parseInt(window.lastscore.childNodes[1].innerHTML));
bot.scores.sort(function (a, b) { return b - a; });
userInterface.updateStats();
}
if (window.autoRespawn) {
bot.connect();
}
}
if (!bot.isBotEnabled || !bot.isBotRunning) {
window.onmousemove = original_onmousemove;
}
userInterface.onFrameUpdate();
if (!bot.isBotEnabled && !window.no_raf) {
window.raf(userInterface.oefTimer);
} else {
setTimeout(
userInterface.oefTimer, (1000 / bot.opt.targetFps) - (Date.now() - start));
}
},
// Quit to menu
quit: function () {
if (window.playing && window.resetGame) {
window.want_close_socket = true;
window.dead_mtm = 0;
if (window.play_btn) {
window.play_btn.setEnabled(true);
}
window.resetGame();
}
},
handleTextColor: function (enabled) {
return '<span style=\"color:' +
(enabled ? 'green;\">enabled' : 'red;\">disabled') + '</span>';
}
};
})(window, document);
// Main
(function (window, document) {
window.play_btn.btnf.addEventListener('click', userInterface.playButtonClickListener);
document.onkeydown = userInterface.onkeydown;
window.onmousedown = userInterface.onmousedown;
window.addEventListener('mouseup', userInterface.onmouseup);
// Hide top score
userInterface.hideTop();
// force server
userInterface.initServerIp();
userInterface.server.addEventListener('keyup', function (e) {
if (e.keyCode === 13) {
e.preventDefault();
window.play_btn.btnf.click();
}
});
// Overlays
userInterface.initOverlays();
// Load preferences
userInterface.loadPreference('logDebugging', false);
userInterface.loadPreference('visualDebugging', false);
userInterface.loadPreference('autoRespawn', false);
userInterface.loadPreference('mobileRender', false);
window.nick.value = userInterface.loadPreference('savedNick', 'Slither.io-bot');
// Listener for mouse wheel scroll - used for setZoom function
document.body.addEventListener('mousewheel', canvas.setZoom);
document.body.addEventListener('DOMMouseScroll', canvas.setZoom);
// Set render mode
if (window.mobileRender) {
userInterface.toggleMobileRendering(true);
} else {
userInterface.toggleMobileRendering(false);
}
// Unblocks all skins without the need for FB sharing.
window.localStorage.setItem('edttsg', '1');
// Remove social
window.social.remove();
// Maintain fps
//setInterval(userInterface.framesPerSecond.fpsTimer, 80);
// Start!
userInterface.oefTimer();
})(window, document);
function updateElem() {
currServ = bso ? bso.ip + ':' + bso.po : 'None';
var x = snake ? parseInt(snake.xx) : 0;
var y = snake ? parseInt(snake.yy) : 0;
topElem.textContent = 'Server: ' + currServ + ', ' + 'X: ' + x + ', Y: ' + y;
}
function force(elem) {
var splitArr = elem.value.split(':');
if (splitArr.length > 1) forceServer((splitArr[0] || ''), (splitArr[1] || ''));
};
var topElem = document.createElement('span');
window.onload = function(){
topElem.style.position = 'fixed';
topElem.style.zIndex = '666';
topElem.style.top = '5px';
topElem.style.textAlign = 'center';
topElem.style.width = '100%';
topElem.style.fontSize = '20px';
topElem.style.fontFamily = 'Verdana';
topElem.style.color = '#FFF';
document.body.appendChild(topElem);
var parent = document.getElementById('playh');
var div = document.createElement('div');
var input = document.createElement('input');
var button = document.createElement('span');
var currServ = 'None';
div.style.width = '244px';
div.style.margin = '14px auto';
div.style.background = '#4c447c none repeat scroll 0% 0%';
div.style.boxShadow = '0px 6px 50px rgb(0, 0, 0)';
div.style.border = '2px solid rgba(0, 0, 0, 1)';
div.style.borderRadius = '29px';
div.style.display = 'block';
div.style.padding = '5px';
div.style.fontFamily = 'Verdana';
div.style.position = 'relative';
input.type = 'text';
input.placeholder = 'IP-Address:Port';
input.style.margin = '2px';
input.style.background = 'rgba(0, 0, 0, 0) none repeat scroll 0 0';
input.style.border = '0 none';
input.style.color = '#e0e0ff';
input.style.fontSize = '15px';
button.textContent = 'Go!';
button.style.cursor = 'pointer';
button.style.color = '#FFF';
button.style.borderRadius = '24px';
button.style.margin = '2px';
button.style.padding = '2px 6px';
button.style.background = 'linear-gradient(180deg, #9DA, #485)';
button.onclick = function() { force(input); };
div.appendChild(input);
div.appendChild(button);
parent.appendChild(div);
timerID = setInterval(function () {
updateElem();
}, 1000);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment