<!doctype html> <html> <head> <title>🏓 Pong</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { height: 100vh; width: 100vw; overflow: hidden; } canvas {border: black solid 1px; top:-1px; left:-1px; position: absolute;} div { position: fixed; top: 0; right: 0; width: 130px; height: 60px; background: rgba(0, 0, 0, .5); color: white; padding: 5px; font-family: monospace; z-index: 100; } </style> </head> <body> <div id="debug">FPS: <fps>...</fps><br/>Ping: <ping>...</ping><br />ID: <id>...</id></div> <canvas id="test"></canvas> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script> /** Initialize Canvas **/ var c = document.getElementById("test"); var ctx = c.getContext("2d"); c.width = $(window).width(); c.height = $(window).height(); /** FPS Vars **/ var lastCalledTime, lastFPSshown, fps; /** Global Configuration **/ var localcoop = false, // Local Coop in 1 window CURRENTLY BORKEN [WONTFIX] width = $(window).width(), height = $(window).height(); /** Prop-Config **/ var cube_width = 50, cube_height = 200, cube_speed = 7, ball_size = 15, ball_speed = 8, ballX = cube_width+ball_size, ballY = cube_height/2, ball_owner = 1, ball_VX = 0, ball_VY = 0, ball_maxAngle = 5*Math.PI/12; /** Game Variables **/ var uid, // Unique-ID pid, // PlayerID 0 == Specator, 1 == Player 1, 2 == Player 2 pwin = 0, pwinanim = 0; leftY = 0, rightY = 0, leftV = 0, rightV = 0, leftColor = 'red', rightColor = 'blue', score = [0,0], pressedkeys = [], loggedkeys = [38, 40, 83, 87], gameState = 0; // 0 == Waiting for Players, 1 == Countdown, 2 == inGame, 3 == ??Win?? var Localization = { player : 'Spieler', spectator : 'Zuschauer', spectatormode : 'Zuschauer-Modus', countdown : 'Starte in ', countdownsuf : ' Sekunden', waiting : 'Warte auf Spieler', win : ' hat gewonnen!' } /** Initalize Socketio **/ var socket = io(); /** ############## **/ /** SERVER EVENTS **/ /** ############# **/ socket.on('handshake', function(id, state, c_width, c_height, sp, score, fn){ $('id').text(id); uid = id; gameState = state; cube_width = c_width; cube_height = c_height; cube_speed = sp; score = score; fn($(window).width(), $(window).height()); }); socket.on('setpid', function(id){ pid = id; }); socket.on('size', function(w, h){ width = w; height = h; c.width = w; c.height = h; text = Localization.countdown + 5 + Localization.countdownsuf; }); socket.on('gamestate', function(state){ // console.log('new GameState: ' + state); gameState = state; }); socket.on('score', function(val){ score = val; }); /** Serversided Countdown to prevent desync **/ socket.on('countdown', function(i){ text = Localization.countdown + i + Localization.countdownsuf; }); socket.on('win', function(id, data){ pwin = id; console.log('Additional Data: ' + data); }); socket.on('paddlepos', function(pos, val, y){ if(pos == 1){ leftV = val; leftY = y; } if(pos == 2){ rightV = val; rightY = y; } (leftY); }); socket.on('ballpos', function(data){ ballX = data['x']; ballY = data['y']; ball_VX = data['vx']; ball_VY = data['vy']; ball_speed = data['speed']; ball_owner = data['owner']; }); /** Print text in local console **/ socket.on('debug', function(arg){ console.log(arg); }); /** ############ **/ /** DEBUG TOOLS **/ /** ########### **/ /** Meassure Ping and show **/ setInterval(function(){ var connTime = Date.now(); socket.emit('appping', function(data){ $('ping').text(Date.now()-connTime + 'ms'); }); }, 1000); var texti = 0, nativtext = Localization.waiting, text = nativtext; function draw() { /** FPS Calculation **/ if(!lastCalledTime) { lastCalledTime = Date.now(); fps = 0; } delta = (Date.now() - lastCalledTime)/1000; lastCalledTime = Date.now(); fps = 1/delta; /** Show FPS in Debug-Window **/ if(!lastFPSshown) { lastFPSshown = Date.now(); $('fps').text('...'); } if(lastFPSshown+1000 < Date.now()) { lastFPSshown = Date.now(); $('fps').text(Math.round(fps)); /** Use this timer to display loading indicator **/ if(gameState == 0) switch(texti){ case 0: text = nativtext; texti++; break; case 1 : case 2 : text+='.'; texti++; break; case 3 : text+='.'; texti = 0; break; } } /** ############## **/ /** CLIENT RENDER **/ /** ############# **/ /** Clear Drawing-Region **/ ctx.fillStyle = 'white'; ctx.fillRect(0, 0, width, height); /** Points **/ ctx.fillStyle = 'black'; ctx.textAlign = 'center'; var scoretxt = score[0] + ' : ' + score[1]; if(pwin != 0 && gameState == 3){ ctx.font = '80px monospace'; ctx.fillText(Localization.player + ' ' + pwin + Localization.win, width/2, 310); ctx.font = '80px monospace'; ctx.fillText(scoretxt, width/2, 190); } else { ctx.font = '40px monospace'; ctx.fillText(scoretxt, width/2, 70); } if(gameState < 2){ ctx.fillStyle = 'black'; ctx.font = '30px monospace'; ctx.textAlign = 'center'; ctx.fillText(text, width/2, height/2-30); } ctx.fillStyle = 'black'; ctx.font = '15px monospace'; ctx.textAlign = 'center'; if(gameState == 2 && pid == 0){ ctx.fillText(Localization.spectatormode, width/2, 25); } if(pid == 1 || pid == 2){ ctx.fillText(Localization.player + ' ' + pid, width/2, 25); } /** Draw Cube **/ /** Note: Get position by multiplying it with the "Render-Delta" - for same speed on every system **/ // console.log(leftV*delta*60*cube_speed); // leftY -= leftV*delta*60*cube_speed; // rightY -= rightV*delta*60*cube_speed; // NOTE Server is calcualating Position -> Client is only drawing ctx.fillStyle = leftColor; ctx.fillRect(0, leftY, cube_width, cube_height); ctx.fillStyle = rightColor; ctx.fillRect(width-cube_width, rightY, cube_width, cube_height); // NOTE Physics moved to Server // /** Move Ball with Paddle if owned by owner (@roundstart) **/ // if(ball_owner == 1){ // ballX = cube_width+ball_size, // ballY = leftY+cube_height/2; // } // else if(ball_owner == 2){ // ballX = width-cube_width-ball_size, // ballY = rightY+cube_height/2; // } // // /** ############ **/ // /** Ball Physics **/ // /** ############ **/ // // /** Collide with right Paddle or get point and respawn **/ // if(ballX > width - cube_width){ // if(ballY < rightY || ballY > rightY+cube_height){ // console.log('Point for Left'); // ball_VX = 0; // ball_VY = 0; // ball_owner = 1; // } else { // var intersect = ((rightY+cube_height/2)-ballY)/(cube_height/2); // console.log('Intersect with right at:'); // console.log(intersect); // console.log('###########################'); // ball_VX = -ball_speed*Math.cos(intersect*ball_maxAngle); // ball_VY = -ball_speed*Math.sin(intersect*ball_maxAngle); // } // } // // /** Collide with left Paddle or get point and respawn **/ // if(ballX < cube_width){ // if(ballY < leftY || ballY > leftY+cube_height){ // console.log('Point for Right'); // ball_VX = 0; // ball_VY = 0; // ball_owner = 2; // } else { // var intersect = ((leftY+cube_height/2)-ballY)/(cube_height/2); // console.log('Intersect with left at:'); // console.log(intersect); // console.log('###########################'); // ball_VX = ball_speed*Math.cos(intersect*ball_maxAngle); // ball_VY = ball_speed*Math.sin(intersect*ball_maxAngle); // } // } // // // /** Collide with walls **/ // if(ballY <= 0 || ballY >= height-ball_size){ // ball_VY = -ball_VY; // console.log(ballX + ' : ' + ballY); // } /** Draw Ball **/ ctx.fillStyle = 'black'; // ballX += Math.round((1*delta)*60)*ball_VX; // ballY += Math.round((1*delta)*60)*ball_VY; ctx.beginPath(); ctx.arc(ballX, ballY, ball_size, 0, 2*Math.PI); ctx.fill(); } /** ############## **/ /** Input Handlers **/ /** ############## **/ $(window).on('keydown', function(e) { if(localcoop){ switch(e.keyCode) { case 38 : if(leftY <= 0) break; setV(1, 1); break; case 40 : if(leftY >= height-cube_height) break; setV(1, -1); break; case 87 : if(rightY <= 0) break; setV(2, 1); break; case 83 : if(rightY >= height-cube_height) break; setV(2, -1); break; case 37 : case 39 : if(ball_owner != 1) break; shootBall(); break; case 65 : case 68 : if(ball_owner != 2) break; shootBall(); break; } } else if(pid == 1 || pid == 2){ if(gameState == 3) return; if(loggedkeys.indexOf(e.keyCode) > -1 && pressedkeys.indexOf(e.keyCode) < 0) pressedkeys.push(e.keyCode); switch(e.keyCode) { case 38 : case 87 : if(pid == 1){ if(leftY <= 0) break; setV(1, 1); } else if(pid == 2) { if(rightY <= 0) break; setV(2, 1); } break; case 40 : case 83 : if(pid == 1){ if(leftY >= height-cube_height) break; setV(1, -1); } else if(pid == 2){ if(rightY >= height-cube_height) break; setV(2, -1); } break; case 37 : case 39 : case 65 : case 68 : if(ball_owner != pid)break; if(ball_owner == 1) socket.emit('ballshoot', leftY); else if(ball_owner == 2) socket.emit('ballshoot', rightY); break; } } }); $(window).on('keyup', function(e) { if(localcoop){ switch(e.keyCode) { case 38 : case 40 : setV(1, 0); break; case 87 : case 83 : setV(2, 0); break; } }else if(pid == 1 || pid == 2){ if(gameState == 3) return; if(loggedkeys.indexOf(e.keyCode) > -1 && pressedkeys.indexOf(e.keyCode) > -1){ pressedkeys.splice(pressedkeys.indexOf(e.keyCode), 1); if(pressedkeys.length == 0) // Only send v=0 if all keys are up setV(pid, 0); } } }); function setV(pos, val){ var time = Date.now(); if(pos == 1){ leftV = val; if(!localcoop) socket.emit('vchange', pos, val, leftY, time); } if(pos == 2){ rightV = val; if(!localcoop) socket.emit('vchange', pos, val, rightY, time); } } function shootBall() { // NOTE moved to Server // console.log('### Ball shoot event ###'); // /** TODO Tewak and Add MAX_Speed **/ // if(ball_owner == 1){ // ball_VX = ball_speed; // console.log('owner: left - at:' + leftY); // // } // else if(ball_owner == 2){ // ball_VX = -ball_speed; // console.log('owner: right - at:' + rightY); // if(!localcoop) // socket.emit('ballshoot', rightY); // } // ball_owner = 0; } function animloop() { draw(); window.requestAnimationFrame(animloop); } animloop(); </script> </body> </html>