From: Elijah Cohen Date: Thu, 25 Apr 2019 22:49:40 +0000 (-0500) Subject: totally untested game logic complete X-Git-Url: https://git.eli173.com/?a=commitdiff_plain;h=cd4a164ed8bb5447bf9aced0a7eb6e174dc3b9c8;p=pong_br totally untested game logic complete now to put it all together? test it out? who knows what will happen? --- diff --git a/.gitignore b/.gitignore index e28b64b..c6a1d47 100644 --- a/.gitignore +++ b/.gitignore @@ -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~ diff --git a/NOTES.org b/NOTES.org index 5f9753e..d7f013c 100644 --- 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 index 0000000..9efe6bf --- /dev/null +++ b/server/ball.js @@ -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 index 6f10481..0000000 --- a/server/basics.js +++ /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 index 0000000..0cedeae --- /dev/null +++ b/server/coord.js @@ -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 index 0000000..bd5b51c --- /dev/null +++ b/server/endpoints.js @@ -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; diff --git a/server/field.js b/server/field.js index fbdd17a..8b893c3 100644 --- a/server/field.js +++ b/server/field.js @@ -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(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(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; +} + diff --git a/server/gamestate.js b/server/gamestate.js index a72145e..2e49170 100644 --- a/server/gamestate.js +++ b/server/gamestate.js @@ -1,21 +1,171 @@ +// 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 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 !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(""); diff --git a/server/paddles.js b/server/paddles.js index e16ccb3..dd26b92 100644 --- a/server/paddles.js +++ b/server/paddles.js @@ -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); }