<canvas id="c"></canvas>
<div id=options class=closed>
<p><span>rotVel</span><span><input type=range id=rotVel_input min=0 max=.1 value=.01 step=.002 /></span>
<p><span>zoom</span><span><input type=range id=zoom_input min=.1 max=5 value=1 step=.1 /></span>
<p><span>showDots</span><span><input type=checkbox id=showDots_input /></span>
<p id=toggle_options>close/open options</p>
</div>
<p id=instruction>Use your arrow keys</p>
canvas {
position: absolute;
top: 0;
left: 0;
}
div#options {
position: absolute;
top: 0;
transition: top .5s;
color: #aca;
font-size: 15px;
font-family: monospace;
width: 250px;
background-color: rgba(10,10,10,.8);
}
div#options.closed {
top: -140px;
}
div#options > p {
display: flex;
}
div#options > p > span:first-child {
text-align: right;
padding-right: 10px;
width: 90px;
}
div#options > p > span:nth-child(2){
width: 150px;
}
div#options > p:last-child {
display: inline-block;
width: 250px;
text-align: center;
color: white;
cursor: pointer;
}
p#instruction {
position: absolute;
bottom: 0;
width: 300px;
left: calc( 50% - 150px );
color: rgb(0,255,0);
font-family: monospace;
text-align: center;
}
var c = document.getElementById( 'c' ),
w = c.width = window.innerWidth,
h = c.height = window.innerHeight,
ctx = c.getContext( '2d' ),
opts = {
rotVel: .01,
color: 'rgba(0,200,0,.4)',
gameColor: 'rgba(0,255,0,.8)',
textColor: 'rgba(0,200,0,.4)',
depth: 250,
focalLength: 250,
vanishPoint: {
x: w / 2,
y: h / 2
},
zoom: 1,
showDots: false,
// game
playerSpeed: .01,
ballSpeed: .004,
respawnTime: 60,
startX: -40,
startY: -40,
addedX: 80,
addedY: 80,
startZ: 0
},
pointData, lineData,
points = [],
lines = [],
pong = {
paddle: {
width: .03,
height: .2
},
player: {
x: .05,
y: .4
},
computer: {
x: .9,
y: .4
},
ball: {
x: .475,
y: .475,
width: .05,
height: .05,
vx: opts.ballSpeed,
vy: opts.ballSpeed,
respawnTime: opts.respawnTime
},
controls: {
up: false,
down: false
},
points: [],
lines: []
},
rot = 0,
cos = 1,
sin = 0,
tick = 0;
function Point( x, y, z, index ){
this.x = x;
this.y = y;
this.z = z;
this.index = index;
}
Point.prototype.setScreen = function(){
var x = this.x,
y = this.y,
z = this.z,
x1 = x;
// rotate in y
x = x * cos - z * sin;
z = z * cos + x1* sin;
// translate z
z += opts.depth;
var scale = opts.focalLength / z;
this.scale = scale;
this.sx = x * scale; // screen X
this.sy = y * scale;
if( !opts.showDots )
return;
ctx.fillStyle = opts.textColor;
ctx.font = scale * 6 + 'px "Courier New"'
ctx.fillText( this.index, this.sx - scale * 3, this.sy - scale * 3 );
}
function Line( points ){
this.points = points;
}
Line.prototype.draw = function(){
ctx.moveTo( this.points[ 0 ].sx, this.points[ 0 ].sy );
this.points.map( point => ctx.lineTo( point.sx, point.sy ) );
}
function anim(){
window.requestAnimationFrame( anim );
++tick;
rot += opts.rotVel;
cos = Math.cos( rot );
sin = Math.sin( rot );
ctx.fillStyle = 'rgba(0,0,0,1)';
ctx.fillRect( 0, 0, w, h );
ctx.translate( opts.vanishPoint.x, opts.vanishPoint.y );
ctx.scale( opts.zoom, opts.zoom );
ctx.lineCap = 'square';
ctx.miterLimit = 2;
ctx.strokeStyle = opts.color;
ctx.beginPath();
points.map( point => point.setScreen() );
lines.map( line => line.draw() );
ctx.stroke();
updatePong();
ctx.strokeStyle = opts.gameColor;
ctx.beginPath();
pong.points.map( point => point.setScreen() );
pong.lines.map( line => line.draw() );
ctx.stroke();
ctx.scale( 1/opts.zoom, 1/opts.zoom )
ctx.translate( -opts.vanishPoint.x, -opts.vanishPoint.y );
}
function updatePong(){
if( pong.ball.respawnTime === 0 ){
var preva1 = pong.ball.x; // prev a1
pong.ball.x += pong.ball.vx;
pong.ball.y += pong.ball.vy;
var a1 = pong.ball.x,
A1 = a1 + pong.ball.width,
b1 = pong.ball.y,
B1 = b1 + pong.ball.height;
if( pong.controls.up ){
pong.player.y -= opts.playerSpeed;
if( pong.player.y < 0 )
pong.player.y = 0;
} else if( pong.controls.down ){
pong.player.y += opts.playerSpeed;
if( pong.player.y + pong.paddle.height > 1 )
pong.player.y = 1 - pong.paddle.height;
}
var b2 = pong.player.y,
A2 = pong.player.x + pong.paddle.width,
B2 = b2 + pong.paddle.height;
if( pong.computer.y > b1 )
pong.computer.y -= opts.playerSpeed;
else if( pong.computer.y + pong.paddle.height < B1 )
pong.computer.y += opts.playerSpeed;
if( pong.computer.y < 0 )
pong.computer.y = 0;
else if( pong.computer.y + pong.paddle.height > 1 )
pong.computer.y = 1 - pong.paddle.height;
var a3 = pong.computer.x,
b3 = pong.computer.y,
B3 = b3 + pong.paddle.height;
// 1 = ball, 2 = player, 3 = computer
if( pong.ball.vx < 0 ){
if( preva1 > A2 && a1 < A2 && ( B1 > b2 && b1 < B2 ) )
collidePaddle( b2 )
else if( a1 < 0 )
resetPongBall( 1 );
} else {
if( preva1 + pong.ball.width < a3 && A1 > a3 && ( B1 > b3 && b1 < B3 ) )
collidePaddle( b3 );
else if( A1 > 1 )
resetPongBall( -1 );
}
if( b1 < 0 || B1 > 1 )
pong.ball.vy *= -1;
} else --pong.ball.respawnTime;
// aA is easier to distinguish than xX
var a = pong.player.x,
b = pong.player.y,
A = a + pong.paddle.width,
B = b + pong.paddle.height;
pongTransform( 0, a, b );
pongTransform( 1, A, b );
pongTransform( 2, A, B );
pongTransform( 3, a, B );
a = pong.computer.x;
b = pong.computer.y;
A = a + pong.paddle.width;
B = b + pong.paddle.height;
pongTransform( 4, a, b );
pongTransform( 5, A, b );
pongTransform( 6, A, B );
pongTransform( 7, a, B );
a = pong.ball.x;
b = pong.ball.y;
A = a + pong.ball.width;
B = b + pong.ball.height;
pongTransform( 8, a, b );
pongTransform( 9, A, b );
pongTransform( 10,A, B );
pongTransform( 11,a, B );
}
function collidePaddle( b ){
var proportion = ( pong.ball.y - b ) / ( pong.paddle.height + pong.ball.height );
pong.ball.vx *= -1.05;
pong.ball.vy += ( proportion - .5 ) * opts.ballSpeed;
}
function resetPongBall( direction ){
pong.ball.respawnTime = opts.respawnTime;
pong.ball.x = .5 - pong.ball.width / 2;
pong.ball.y = .5 - pong.ball.height / 2;
pong.ball.vx = opts.ballSpeed * direction;
pong.ball.vy = opts.ballSpeed * ( Math.random() < .5 ? 1 : -1 );
}
function pongTransform( index, x, y ){
var point = pong.points[ index ];
point.x = opts.startX + x * opts.addedX;
point.y = opts.startY + y * opts.addedY;
point.z = opts.startZ;
}
function init(){
for( var i = 0; i < pointData.length; i += 3 ){
var x = pointData[ i ],
y = pointData[ i + 1 ],
z = pointData[ i + 2 ];
// space for other initial transformations like scaling here
x *= 40;
y *= 40;
z *= 40;
points.push( new Point( x, y, z, i / 3 ) );
}
// hope this is not too confusing
for( var i = 0; i < lineData.length; ++i )
lines.push( new Line( lineData[ i ].map( index => points[ index ] ) ) );
for( var i = 0; i < 4*3; ++i )
pong.points.push( new Point );
for( var i = 0; i < 3; ++i )
pong.lines.push( new Line( [
pong.points[ i * 4 ],
pong.points[ i * 4 + 1 ],
pong.points[ i * 4 + 2 ],
pong.points[ i * 4 + 3 ],
pong.points[ i * 4 ]
] ) );
anim();
}
window.addEventListener( 'resize', function(){
w = c.width = window.innerWidth;
h = c.height = window.innerHeight;
opts.vanishPoint.x = w / 2;
opts.vanishPoint.y = h / 2;
});
window.addEventListener( 'keydown', function( e ){
// [ A, down, S, J, right ]
if( [ 65, 40, 83, 74, 39 ].indexOf( e.keyCode ) !== -1 ){
pong.controls.down = true;
pong.controls.up = false;
}
// [ W, D, up, K, left ]
if( [ 87, 68, 38, 75, 37 ].indexOf( e.keyCode ) !== -1 ){
pong.controls.up = true;
pong.controls.down = false;
}
if( e.keyCode === 32 )
opts.showDots = !opts.showDots;
e.preventDefault();
});
window.addEventListener( 'keyup', function( e ){
// [ A, down, S, J, right ]
if( [ 65, 40, 83, 74, 39 ].indexOf( e.keyCode ) !== -1 )
pong.controls.down = false;
// [ W, D, up, K, left ]
if( [ 87, 68, 38, 75, 37 ].indexOf( e.keyCode ) !== -1 )
pong.controls.up = false;
e.preventDefault();
});
rotVel_input.addEventListener( 'mousemove', function(){
opts.rotVel = +rotVel_input.value;
});
zoom_input.addEventListener( 'mousemove', function(){
opts.zoom = +zoom_input.value;
});
showDots_input.addEventListener( 'change', function(){
opts.showDots = showDots_input.checked;
});
toggle_options.addEventListener( 'click', function(){
options.classList.toggle( 'closed' );
});
// found out manually, using showDots: true
pointData = [
// computer screen 0-3
-1,-1, 0,
-1, 1, 0,
1, 1, 0,
1,-1, 0,
// computer frame 4-7
-1.3,-1.3, 0,
-1.3, 1.3, 0,
1.3, 1.3, 0,
1.3,-1.3, 0,
// computer back 8-11
-.8,-.8, 1.5,
-.8, .8, 1.5,
.8, .8, 1.5,
.8,-.8, 1.5,
// connector to stand 12-18
// top 12-15
.6,1.15,.5,
-.6,1.15,.5,
-.6, .98, 1,
.6, .98, 1,
// bottom 16-19
.6,1.7,.5,
-.6,1.7,.5,
-.6,1.7, 1,
.6,1.7, 1,
// stand 20-27
// top 20-23
1.8,1.7,-.1,
-1.8,1.7,-.1,
-1.8,1.7,1.8,
1.8,1.7,1.8,
// bottom 24-27
1.8,1.9,-.1,
-1.8,1.9,-.1,
-1.8,1.9,1.8,
1.8,1.9,1.8,
// keyboard 28-43
// frame 28-35
// top 28-31
2,1.6,-0.8,
2,1.7,-2.3,
-2,1.7,-2.3,
-2,1.6,-0.8,
// bottom 32-35
2,1.9,-0.8,
2,1.9,-2.3,
-2,1.9,-2.3,
-2,1.9,-0.8,
// keys 36-43
// letters + nums 36-39
0.5,1.612, -1,
0.5,1.688,-2.1,
-1.8,1.688,-2.1,
-1.8,1.612, -1,
// numpad 40-43
1.8,1.612, -1,
1.8,1.688,-2.1,
0.9,1.688,-2.1,
0.9,1.612, -1,
// tower
// top
2.2,-1.2,2.7,
2.2,-1.2,0.3,
3.5,-1.2,0.3,
3.5,-1.2,2.7,
// bottom
2.2,1.9,2.7,
2.2,1.9,0.3,
3.5,1.9,0.3,
3.5,1.9,2.7,
// inputs
2.5,-0.8,.3,
3.2,-0.8,.3,
2.5,-0.5,.3,
3.2,-0.5,.3,
2.5,-0.2,.3,
3.2,-0.2,.3,
3.2, 0.3,.3,
2.5, 0.3,.3,
2.5, 0.6,.3,
3.2, 0.6,.3,
2.8, 0.9,.3,
3.2, 0.9,.3,
3.2, 1.2,.3,
2.8, 1.2,.3
];
lineData = [
// computer
[ 0, 1, 2, 3, 0 ],
[ 4, 5, 6, 7, 11, 8, 4, 7 ],
[ 11, 10, 9, 8 ],
[ 6, 10 ],
[ 9, 5 ],
// connector
[ 12, 15, 14, 13, 12, 16, 19, 15 ],
[ 19, 18, 14 ],
[ 18, 17, 13 ],
[ 17, 16 ],
// stand
[ 22, 21, 25, 26, 22, 23, 20, 21 ],
[ 25, 24, 27, 26 ],
[ 24, 20 ],
[ 27, 23 ],
// keyboard
[ 31, 30, 29, 28, 31, 35, 34, 33, 32, 35 ],
[ 34, 30 ],
[ 33, 29 ],
[ 32, 28 ],
[ 40, 41, 42, 43, 40 ],
[ 36, 37, 38, 39, 36 ],
// tower
[ 44, 45, 46, 47, 44, 48, 49, 50, 51, 48 ],
[ 51, 47 ],
[ 50, 46 ],
[ 49, 45 ],
[ 52, 53 ],
[ 54, 55 ],
[ 56, 57, 58, 59, 56 ],
[ 60, 61 ],
[ 62, 63, 64, 65, 62 ]
];
init();
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.