/server/basics.js~
/server/field.js~
/server/paddles.js~
+/server/ball.js~
+/server/coord.js~
+/server/endpoints.js~
I'm going to limit the number of players to 10 for the time being, I think anything with more than 20 sides might be a bit too much. 20 might be pushing it actually.
-At the moment I think I've figured out basic matchmaking (the br format pretty much lets me ignore skill levels so I can just throw anyone with anyone), so the main concern is the actual pong gameplay. My biggest challenge is how to shrink the circle when someone dies.
-There's gotta be a decent transformation I can come up with? Probably mostly trig. I've gotta work it so multiple things can go away at the same time.
+
+where do i want the collision checkings to take place? It's gotta be somewhere near the death check?
--- /dev/null
+
+
+const MIN_INIT = 3;
+const MAX_INIT = 5; // initial speed constraints
+
+const RADIUS = 0.5;
+
+const MAX_SPEED = 15;
+
+const SPEED_BUMP = 0.2;
+
+const Coord = require('./coord.js');
+
+function Ball() {
+ // generates a new ball with random direction and speed (within limits)
+ var coord = new Coord(0,0);
+ var angle = Math.random()*2*Math.PI;
+ var speed = Math.random()*(MAX_INIT-MIN_INIT) + MIN_INIT;
+ this.dx = speed*Math.cos(angle);
+ this.dy = speed*Math.sin(angle);
+}
+
+Ball.prototype.speed_up = function() {
+ // this is ez with proportions
+ var speed = Math.sqrt(this.dx*this.dx + this.dy*this.dy);
+ if (speed > MAX_SPEED)
+ speed = MAX_SPEED;
+ this.dx = (speed*this.dx)/(speed+SPEED_BUMP);
+ this.dy = (speed*this.dy)/(speed+SPEED_BUMP);
+}
+
+Ball.prototype.get_angle = function() {
+ // gets angle from the origin
+ return Math.atan2(this.coord.y, this.coord.x);
+}
+Ball.prototype.radius = RADIUS;
+
+module.exports = Ball;
+++ /dev/null
-
-
-
-var Coord(x,y) {
- this.x = x;
- this.y = y;
-}
--- /dev/null
+
+
+
+
+
+var Coord(x,y) {
+ this.x = x;
+ this.y = y;
+}
+
+
+Coord.prototype.dist2(c2) {
+ // returns square of the distance
+ var dx = this.x-c2.x;
+ var dy = this.y-c2.y;
+ return dx*dx + dy*dy;
+}
+
+Coord.prototype.rotate(th) {
+ // rotates about angle, returns new value
+ var new_x = this.x*Math.cos(th) - this.y*Math.sin(th);
+ var new_y = this.x*Math.sin(th) + this.y*Math.cos(th);
+ this.x = new_x;
+ this.y = new_y;
+}
+
+module.exports = Coord;
--- /dev/null
+
+
+var Endpoints = function(f,s,id) {
+ // f,s are first, second coordinates, in increasing angle
+ // id corresponds to a player, or -1 or smth if n/a
+ // these endpoints enclose a paddle
+ this.f = f;
+ this.s = s;
+ this.id = id;
+}
+
+
+module.exports = Endpoints;
-const RADIUS = 10; // completely arbitrary actually...
+const BOARD_RADIUS = 10; // completely arbitrary actually...
// for generating/using the actual playing field of the game, I guess it'll be newly generated on each frame? the math is simple enough...
-var Endpoints = function(f,s,a) {
- // f,s are first, second coordinates, in increasing angle
- // a is alive
- // these endpoints enclose a paddle
- this.f = f;
- this.s = s;
- this.isAlive = a;
-}
-var Coord(x,y) {
- this.x = x;
- this.y = y;
-}
+const Coord = require('./coord.js')
+const Endpoints = require('./endpoints.js')
+
var genEndpoints = function(n ,dead) {
/**
/* takes the number of players total (that the game started with, should be NUM_PLAYERS),
/* and the array of the dead and dying
+ /* does id's in a bad but expectable way
*/
// so i'll start from a radius out a zero radians
var endpoints = [];
}
var theta = 0;
var dtheta = players_length/(2*Math.PI);
- var coord = new Coord(RADIUS,0);
+ var coord = new Coord(BOARD_RADIUS,0);
for(var i=0; i<n; i++) {
var deadStatus = dead.find(e=>(e.id==i));
if((deadStatus !== undefined) && (deadStatus.time > 0)) {
- var r = RADIUS;
+ var r = BOARD_RADIUS;
theta += (deadStatus.time/DYING_TIME_IN_FRAMES)/2;
var pt1 = new Coord(r*Math.cos(theta), r*Math.sin(theta));
theta += (deadStatus.time/DYING_TIME_IN_FRAMES)/2;
var pt2 = new Coord(r*Math.cos(theta), r*Math.sin(theta));
- endpoints.append(new Endpoints(pt1, pt2, false));
+ endpoints.push(new Endpoints(pt1, pt2, n));
}
else {
theta += dtheta/2;
var pt1 = new Coord(r*Math.cos(theta), r*Math.sin(theta));
theta += dtheta/2;
var pt2 = new Coord(r*Math.cos(theta), r*Math.sin(theta));
- endpoints.append(new Endpoints(pt1, pt2, true));
+ endpoints.push(new Endpoints(pt1, pt2, n));
}
}
return endpoints;
}
-var Field = function(endpoints, paddles)
+var endpointNegatives = function(endpoints) {
+ // generates the opposite of genEndpoints
+ // i.e. genEndpoints gives the spaces where the paddles live,
+ // and this gives the endpoints enclosing walls
+ var newpoints = [];
+ newpoints.push(new Endpoints(endpoints[endpoints.length-1].s, endpoints[0].f, -1));
+ for(var i=1;i<endpoints.length;i++) {
+ newpoints.push(new Endpoints(endpoints[i-1].s, endpoints[i].f, -1));
+ }
+ return newpoints;
+}
+
+var AnglePair = function(f,s,id) {
+ this.f = f;
+ this.s = s;
+ this.id = id;
+}
+var angles = function(n, dead, thresh) {
+ // gives angle pairs for the thresholds and whatnot.
+ var angs = [];
+ var players_length = n - dead.length;
+ for(var d in dead) {
+ players_length += d.time/DYING_TIME_IN_FRAMES;
+ }
+ var theta = 0;
+ var dtheta = players_length/(2*Math.PI);
+ var coord = new Coord(BOARD_RADIUS,0);
+ for(var i=0; i<n; i++) {
+ var deadStatus = dead.find(e=>(e.id==i));
+ if((deadStatus !== undefined) && (deadStatus.time > 0)) {
+ var r = BOARD_RADIUS;
+ theta += (deadStatus.time/DYING_TIME_IN_FRAMES)/2;
+ var t1 = theta;
+ theta += (deadStatus.time/DYING_TIME_IN_FRAMES)/2;
+ var t2 = theta
+ angs.push(new AnglePair(t1, t2, n));
+ }
+ else {
+ theta += dtheta/2;
+ var t1 = theta;
+ theta += dtheta/2;
+ var t2 = theta;
+ angs.push(new anglepair(t1, t2, n));
+ }
+ }
+ return angs;
+}
+
+// NOTE: inputs all get updated, forever will be length n, should work fine as-is
+
const DYING_TIME_IN_FRAMES = 100;
+const BOARD_RADIUS = 10; /// ERROR! DUPLICATE!
+const OOB_THRESH = 1; // out-of-bounds threshold
+const ANGLE_THRESH = 0.2 //radians, needs to acct for various rotatings going on... can prolly wing it
+
+const PADDLE_MVT_BONUS = 0.1; // why this value? who knows. the extra speed from paddles in motion
+
+// const BOUNCE_DISTANCE = 0.1; // probably needs to be lowered...
+// actually not needed, this data is in ball.radius...
+
+const Coord = require('./coord.js');
+const Ball = require('./ball.js');
+
function Dead(id) {
this.id = id;
this.time = DYING_TIME_IN_FRAMES;
}
function GameState(n) {
+ this.numPlayers = n;
this.numLiving = n;
this.dead = [];
- this.inputs = [];
+ this.paddles = [];
+ for(var i=0;i<n;i++) {
+ this.paddles.push(new Paddle(n));
+ }
+ this.balls = [];
+ for(var i=0;i<starting_balls(n);i++) {
+ this.balls.push(new Ball();)
+ }
+}
+
+function starting_balls(n) {
+ // gives back the number of balls to start with given n players
+ // change this function for either more or less fun
+ return n;
+}
+
+
+function nearest_point_on_line(c, ep) {
+ // finds the point on the line defined by ep closest to center c
+ if(ep.f.x == ep.s.x) {
+ // vertical line, undef slope
+ return new Coord(ep.f.x, c.y);
+ }
+ if(ep.f.y == ep.s.y) {
+ // horizontal line, zero slope
+ return new Coord(c.x, ep.f.y);
+ }
+ var sl = (ep.f.y-ep.s.y)/(ep.f.x-ep.s.x);
+ var sr = 1/sl;
+ var x_int = (sl*ep.f.x - sr*c.x + c.y - ep.f.y)/(sl-sb);
+ var y_int = sr*(x_int-c.x) + c.y;
+ return new Coord(x_int, y_int);
}
GameState.prototype.update = function(inputs) {
- this.inputs = inputs;
- // TODO: all of the rest of this
+ // inputs is an array of the characters from all the players (even dead? yeah)
+ // move the paddles
+ for(var i=0;i<this.paddles.length;i++) {
+ paddles[i].move(inputs[paddles[i].id]); //hacky and bad, requires the inputs be
+ }
+ //move the balls
+ for(var ball in this.balls) {
+ ball.coord.x += ball.dx;
+ ball.coord.y += ball.dy;
+ }
+ //
+ var endpoints = genEndpoints(this.numPlayers, this.dead);
+ var borders = endpointNegatives(endpoints);
+ var deadzones = endpoints.filter(ep => ep.isDead);
+ var walls = borders.concat(deadzones);
+ // check for collisions
+ for(var ball in this.balls) {
+ // (check the fixt edges first, then the paddles I guess
+ var collided = false;
+ for(var i=0; (i<walls.length) && !collided; i++) {
+ var nearest = nearest_point_on_line(ball.coord, walls[i]);
+ var dist2 = ball.coord.dist2(nearest);
+ if(dist2 < (ball.radius*ball.radius)) {
+ // we have a collision!
+ collided = true;
+ var vec = [walls[i].f.x-walls[i].s.x, walls[i].f.y-walls[i].s.y];
+ var slope_angle = Math.atan2(vec[1],vec[0]);
+ // rotate, reflect, rotate back
+ var vel_coord = new Coord(ball.dx, ball.dy);
+ vel_coord.rotate(-slope_angle);
+ vel_coord.x = -vel_coord.x;
+ vel_coord.rotate(slope_angle);
+ ball.dx = vel_coord.x;
+ ball.dy = vel_coord.y;
+ // increase speed slightly
+ ball.speed_up();
+ }
+ }
+ var livingzones = endpoints.filter(ep => !ep.isDead);
+ for(var lz in livingzones) {
+ // get corresponding paddle
+ // should be guaranteed to find one.... so...
+ var paddle = this.paddles.find(p => p.id ==lz.id);
+ if((paddle !== undefined) && !collided) {
+ var padends = paddle.getEndpoints(lz);
+ var nearest = nearest_point_on_line(ball.coord, padends);
+ var dist2 = ball.coord.dist2(nearest);
+ if(dist2 < (ball.radius*ball.radius)) {
+ // we have a collision!
+ collided = true;
+ var vec = [padends.f.x-padends.s.x, padends.f.y-padends.s.y];
+ var slope_angle = Math.atan2(vec[1],vec[0]);
+ // rotate, reflect, rotate back
+ var vel_coord = new Coord(ball.dx, ball.dy);
+ vel_coord.rotate(-slope_angle);
+ vel_coord.x = -vel_coord.x;
+ // note: any 'too fast' errors will be solved in the 'speed_up' method, so i need not worry here
+ if(paddle.direction == 'u') {
+ vel_coord.x += PADDLE_MVT_BONUS;
+ }
+ else if(paddle.direction == 'd') {
+ vel_coord.x -= PADDLE_MVT_BONUS;
+ }
+ vel_coord.rotate(slope_angle);
+ ball.dx = vel_coord.x;
+ ball.dy = vel_coord.y;
+ // increase speed slightly
+ ball.speed_up();
+ }
+ }
+ }
+ }
+ // check for deaths
+ // OK I'M HERE AFAICT
+ // i don't need to check where any of the paddles are, I just need to check the spaces behind the paddles (±)
+ var zero = new Coord(0,0);
+ var oobs = this.balls.filter(b => (b.dist2(zero)>(BOARD_RADIUS+OOB_THRESH)));
+ var angs = angles(this.numPlayers, this.dead, OOB_THRESH);
+ for(var oob in oobs) {
+ var oobth = oob.get_angle();
+ for(var ang in angs) {
+ // normalise angle pair...
+ // this gon be inefficient sigh
+ var a1 = ang.f;
+ var a2 = ang.s;
+ while(oobth > (a1-2*Math.PI))
+ oobth -= 2*Math.PI;
+ while( a2 > (oobth-2*Math.PI))
+ a2 -= 2*Math.PI;
+ if((a2-a1) < 2*Math.PI) { // it's in between! (I should check my math...)
+ // check if not already dead (in case multi balls out at same time etc)
+ if(!this.dead.some(x => x.id == ang.id)) {
+ this.dead.push(new Dead(ang.id));
+ /* TODO: socket cleanup for dead player */
+ }
+ }
+ }
+ // remove the ball
+ var idx = this.balls.indexOf(oob);
+ this.balls.splice(idx, 1);
+ }
+ // spawn new balls? sure why not. maybe tweak this for more or less fun later on
+ if(this.balls.length < (this.numPlayers-this.dead.length)-1) {
+ this.balls.push(new Ball());
+ }
}
+
GameState.prototype.getState = function() {
// returns a string suitable to be broadcast to all the players
return this.inputs.join("");
const DPADDLE = 0.1;
+const WIDTH_RATIO = 0.1; // paddle is 1/10th of gap rn
+
+const Coord = require('./coord.js')
+const Endpoints = require('./endpoints.js')
var Paddle = function(id) {
// position is supposed to range between ±1
this.id = id;
this.position = 0;
+ this.direction = 'x';
}
Paddle.prototype.move(direction) {
// direction is either 'u', 'd', or 'x', passed along from the inputs gathered elsewhere
- if((direction=='u') && (this.position < 1))
+ if((direction=='u') && (this.position < 1)) {
this.position += DPADDLE;
- else if((direction=='d') && (this.position > -1))
+ this.direction = 'u';
+ }
+ else if((direction=='d') && (this.position > -1)) {
this.position -= DPADDLE;
+ this.direction = 'd'
+ }
+ else {
+ this.direction = 'x';
+ }
+}
+
+function dist(p1,p2) {
+ var x = p1.x - p2.x;
+ var y = p1.y-p2.y
+ return Math.sqrt(x*x+y*y);
+
+}
+
+Paddle.prototype.getEndpoints(enclosing) {
+ // returns an endpoints object for the paddle
+ // given the desired width of said paddle and the enclosing endpoints
+ var encl_len = dist(enclosing.f, enclosing.s);
+ var pspace_len = encl_len - (2*WIDTH_RATIO*encl_len);
+ var place_on_pspace = pspace_len*(this.position+1)/2;
+ var above_f = pspace_len + WIDTH_RATIO; // distance above the first enclosing point (takes advantage of the increasing in angle thing to determine orientations)
+ var overall_proportion = above_f/encl_len;
+ var vector = [enclosing.s.x-enclosing.f.x, enclosing.s.y-enclosing.f.y];
+ var d = overall_proportion-WIDTH_RATIO;
+ var first = new Coord(vector[0]*d, vector[1]*d);
+ d = overall_proportion+WIDTH_RATIO;
+ var second = new Coord(vector[0]*d, vector[1]*d);
+ return new Endpoints(first, second, this.id);
}