]> git.eli173.com Git - pong_br/commitdiff
totally untested game logic complete
authorElijah Cohen <eli@eli173.com>
Thu, 25 Apr 2019 22:49:40 +0000 (17:49 -0500)
committerElijah Cohen <eli@eli173.com>
Thu, 25 Apr 2019 22:49:40 +0000 (17:49 -0500)
now to put it all together? test it out? who knows what will happen?

.gitignore
NOTES.org
server/ball.js [new file with mode: 0644]
server/basics.js [deleted file]
server/coord.js [new file with mode: 0644]
server/endpoints.js [new file with mode: 0644]
server/field.js
server/gamestate.js
server/paddles.js

index e28b64b35b578a1e7cd81b52f47579f48eecc335..c6a1d474c2853bea1d6a0042ed7d649f247d5341 100644 (file)
@@ -25,3 +25,6 @@ server/node_modules/*
 /server/basics.js~
 /server/field.js~
 /server/paddles.js~
+/server/ball.js~
+/server/coord.js~
+/server/endpoints.js~
index 5f9753e44c95740f1f0a8d7a2cafc57650a36ee7..d7f013c4db915c090123598ef351b9f66ed5294e 100644 (file)
--- a/NOTES.org
+++ b/NOTES.org
@@ -4,6 +4,6 @@ The general outline of the game is that we have some large number of players $n$
 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?
diff --git a/server/ball.js b/server/ball.js
new file mode 100644 (file)
index 0000000..9efe6bf
--- /dev/null
@@ -0,0 +1,38 @@
+
+
+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;
diff --git a/server/basics.js b/server/basics.js
deleted file mode 100644 (file)
index 6f10481..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-var Coord(x,y) {
-    this.x = x;
-    this.y = y;
-}
diff --git a/server/coord.js b/server/coord.js
new file mode 100644 (file)
index 0000000..0cedeae
--- /dev/null
@@ -0,0 +1,27 @@
+
+
+
+
+
+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;
diff --git a/server/endpoints.js b/server/endpoints.js
new file mode 100644 (file)
index 0000000..bd5b51c
--- /dev/null
@@ -0,0 +1,13 @@
+
+
+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;
index fbdd17a95da1d63d065aea5e0f538457edf151ab..8b893c337539685aafcb5e6a2e036ac22373d3b3 100644 (file)
@@ -1,26 +1,18 @@
 
-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 = [];
@@ -30,26 +22,73 @@ var genEndpoints = function(n ,dead) {
     }
     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;
+}
+
index a72145ee29df0c612850df30efba0b25efbb3ada..2e49170968bfa66f82aa83f92df97eed037f2d5d 100644 (file)
+// 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("");
index e16ccb3cabc28efe6cda0d5d155b4425ab7ac1eb..dd26b9205bc37487b8444a23b2e8f5e9d8f812c1 100644 (file)
@@ -1,17 +1,52 @@
 
 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);
 }