<html><head> <style type="text/css"> /* MENU CSS */ * {margin:0;padding:0;font-family: Arial;} h1 {font-size:70;} h2 {margin: 8px 0;} .SmallButton { text-decoration: none; cursor:pointer; color:#555; } .SmallButton:hover { color:#000; } .btn { background: #3498db; background-image: -webkit-linear-gradient(top, #3498db, #2980b9); background-image: -moz-linear-gradient(top, #3498db, #2980b9); background-image: -ms-linear-gradient(top, #3498db, #2980b9); background-image: -o-linear-gradient(top, #3498db, #2980b9); background-image: linear-gradient(to bottom, #3498db, #2980b9); -webkit-border-radius: 15; -moz-border-radius: 15; border-radius: 15px; color: #ffffff; padding: 10px 20px 10px 20px; border: solid #1f628d 5px; text-decoration: none; cursor:pointer; user-select: none; -o-user-select:none; -moz-user-select: none; -khtml-user-select: none; -webkit-user-select: none; } .btn:hover { background: #3cb0fd; background-image: -webkit-linear-gradient(top, #3cb0fd, #3498db); background-image: -moz-linear-gradient(top, #3cb0fd, #3498db); background-image: -ms-linear-gradient(top, #3cb0fd, #3498db); background-image: -o-linear-gradient(top, #3cb0fd, #3498db); background-image: linear-gradient(to bottom, #3cb0fd, #3498db); } input[type='checkbox'] { margin-right: 1em; } </style> <script type="text/javascript" src="Input.js"></script> <script type="text/javascript" src="SlimeAI.js"></script> <script type="text/javascript"> var physicsLog = 0; var TWO_PI = Math.PI*2; var WIN_AMOUNT = 7; // Objects rendered in the slime engine // need an x and a y parameter function newLegacyBall(radius,color) { return { radius:radius, color:color, x:0, y:0, velocityX:0, velocityY:0, rotation:0, render: function() { var xPix = this.x * pixelsPerUnitX; var yPix = courtYPix - (this.y * pixelsPerUnitY); // The original game's ball looked bigger then // it was, so we add 2 pixels here to the radius var radiusPix = this.radius * pixelsPerUnitY + 2; if(ballImage && !legacyGraphics) { this.rotation += this.velocityX / 100; this.rotation = this.rotation % TWO_PI; ctx.translate(xPix, yPix); ctx.rotate(this.rotation); ctx.drawImage(ballImage, -radiusPix, -radiusPix); ctx.setTransform(1,0,0,1,0,0); } else { ctx.fillStyle = legacyBallColor; ctx.beginPath(); ctx.arc(xPix, yPix, radiusPix, 0, TWO_PI); ctx.fill(); } } }; } function newLegacySlime(onLeft,radius,color) { return { onLeft:onLeft, radius:radius, color:color, img:null, x:0, y:0, velocityX:0, velocityY:0, render: function() { var xPix = this.x * pixelsPerUnitX; var yPix = courtYPix - (this.y * pixelsPerUnitY); var radiusPix = this.radius * pixelsPerUnitY; if(this.img && !legacyGraphics) { ctx.drawImage(this.img, xPix - radiusPix, yPix - 38); } else { ctx.fillStyle = this.color; ctx.beginPath(); ctx.arc(xPix, yPix, radiusPix, Math.PI, TWO_PI); ctx.fill(); } // Draw Eyes var eyeX = this.x + (this.onLeft ? 1 : -1)*this.radius/4; var eyeY = this.y + this.radius/2; var eyeXPix = eyeX * pixelsPerUnitX; var eyeYPix = courtYPix - (eyeY * pixelsPerUnitY); ctx.translate(eyeXPix, eyeYPix); ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(0,0, radiusPix/4, 0, TWO_PI); ctx.fill(); // Draw Pupil var dx = ball.x - eyeX; var dy = eyeY - ball.y; var dist = Math.sqrt(dx*dx+dy*dy); var rPixOver8 = radiusPix/8; ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(rPixOver8*dx/dist, rPixOver8*dy/dist, rPixOver8, 0, TWO_PI); ctx.fill(); ctx.setTransform(1,0,0,1,0,0); } }; } var GAME_STATE_RUNNING = 1; var GAME_STATE_POINT_PAUSE = 2; var GAME_STATE_MENU_PAUSE = 3; var GAME_STATE_MENU_PAUSE_BETWEEN_POINTS = 4; var GAME_STATE_SHOW_WINNER = 5; // MENU DATA var menuDiv; var smallMenuDiv; var onePlayer; var nextSlimeIndex; var gameState; // RENDER DATA var ctx; var canvas; var viewWidth; var viewHeight; var courtYPix; var pixelsPerUnitX; var pixelsPerUnitY; var updatesToPaint; var legacySkyColor; var legacyGroundColor; var legacyBallColor; var newGroundColor; var backImage; var backTextColor; var backImages = {}; var ballImage; var gameIntervalObject; var endOfPointText; var greenSlimeImage; var redSlimeImage; var legacyGraphics; // GAME DATA var gameWidth,gameHeight; var ball; var slimeLeft; var slimeRight; var slimeLeftScore; var slimeRightScore; var slimeAI; var updateCount; // RESET every time GAME_STATE_RUNNING is set var leftWon; // GAME CHEAT DATA // NOTE: this data should be reloaded every time the // gameState goes back to GAME_STATE_RUNNING var slowMotion; var logString; function log(msg) { logString += msg + '\n'; } // Game Update Functions function updateSlimeVelocities(s,movement,jump) { if(movement == 0) { s.velocityX = 0; } else if(movement == 1) { s.velocityX = -8; } else if(movement == 2) { s.velocityX = 8; } else { throw "slime movement " + movement + " is invalid"; } if(jump && s.y == 0) { s.velocityY = 31; } } function updateSlimeVelocitiesWithKeys(s,left,right,up) { // update velocities if(keysDown[left]) { if(keysDown[right]) { s.velocityX = 0; } else { s.velocityX = -8; } } else if(keysDown[right]) { s.velocityX = 8; } else { s.velocityX = 0; } if(s.y == 0 && keysDown[up]) { s.velocityY = 31; } } function updateSlimeVelocitiesWithDoubleKeys(s,left1,left2,right1,right2,up1,up2) { // update velocities if(keysDown[left1] || keysDown[left2]) { if(keysDown[right1] || keysDown[right2]) { s.velocityX = 0; } else { s.velocityX = -8; } } else if(keysDown[right1] || keysDown[right2]) { s.velocityX = 8; } else { s.velocityX = 0; } if(s.y == 0 && (keysDown[up1] || keysDown[up2])) { s.velocityY = 31; } } function updateSlime(s, leftLimit, rightLimit) { if(s.velocityX != 0) { s.x += s.velocityX; if(s.x < leftLimit) s.x = leftLimit; else if(s.x > rightLimit) s.x = rightLimit; } if(s.velocityY != 0 || s.y > 0) { s.velocityY -= 2; s.y += s.velocityY; if(s.y < 0) { s.y = 0; s.velocityY = 0; } } } var MAX_VELOCITY_X = 15; var MAX_VELOCITY_Y = 22; function collisionBallSlime(s) { var dx = 2 * (ball.x - s.x); var dy = ball.y - s.y; var dist = Math.trunc(Math.sqrt(dx * dx + dy * dy)); var dVelocityX = ball.velocityX - s.velocityX; var dVelocityY = ball.velocityY - s.velocityY; if(dy > 0 && dist < ball.radius + s.radius && dist > FUDGE) { var oldBall = {x:ball.x,y:ball.y,velocityX:ball.velocityX,velocityY:ball.velocityY}; if(physicsLog > 0) { log("Collision:"); log(" dx " + dx); log(" dy " + dy); log(" dist " + dist); log(" dvx " + dVelocityX); log(" dvy " + dVelocityY); log(" oldBallX " + ball.x); log(" oldBallY " + ball.y); log(" [DBG] s.x : " + s.x); log(" [DBG] s.rad : " + s.radius); log(" [DBG] b.rad : " + ball.radius); log(" [DBG] 0 : " + Math.trunc((s.radius + ball.radius) / 2)); log(" [DBG] 1 : " + Math.trunc((s.radius + ball.radius) / 2)*dx); log(" [DBG] 2 : " + Math.trunc(Math.trunc((s.radius + ball.radius) / 2)*dx/dist)); } ball.x = s.x + Math.trunc(Math.trunc((s.radius + ball.radius) / 2) * dx / dist); ball.y = s.y + Math.trunc((s.radius + ball.radius) * dy / dist); var something = Math.trunc((dx * dVelocityX + dy * dVelocityY) / dist); if(physicsLog > 0) { log(" newBallX " + ball.x); log(" newBallY " + ball.y); log(" something " + something); } if(something <= 0) { ball.velocityX += Math.trunc(s.velocityX - 2 * dx * something / dist); ball.velocityY += Math.trunc(s.velocityY - 2 * dy * something / dist); if( ball.velocityX < -MAX_VELOCITY_X) ball.velocityX = -MAX_VELOCITY_X; else if(ball.velocityX > MAX_VELOCITY_X) ball.velocityX = MAX_VELOCITY_X; if( ball.velocityY < -MAX_VELOCITY_Y) ball.velocityY = -MAX_VELOCITY_Y; else if(ball.velocityY > MAX_VELOCITY_Y) ball.velocityY = MAX_VELOCITY_Y; if(physicsLog > 0) { log(" ballVX " + ball.velocityX); log(" ballVY " + ball.velocityY); } } } } var FUDGE = 5; // not sure why this is needed // returns true if end of point function updateBall() { ball.velocityY += -1; // gravity if(ball.velocityY < -MAX_VELOCITY_Y) { ball.velocityY = -MAX_VELOCITY_Y; } ball.x += ball.velocityX; ball.y += ball.velocityY; collisionBallSlime(slimeLeft); collisionBallSlime(slimeRight); // handle wall hits if (ball.x < 15) { ball.x = 15; ball.velocityX = -ball.velocityX; } else if(ball.x > 985){ ball.x = 985; ball.velocityX = -ball.velocityX; } // hits the post if (ball.x > 480 && ball.x < 520 && ball.y < 140) { // bounces off top of net if (ball.velocityY < 0 && ball.y > 130) { ball.velocityY *= -1; ball.y = 130; } else if (ball.x < 500) { // hits side of net ball.x = 480; ball.velocityX = ball.velocityX >= 0 ? -ball.velocityX : ball.velocityX; } else { ball.x = 520; ball.velocityX = ball.velocityX <= 0 ? -ball.velocityX : ball.velocityX; } } // Check for end of point if(ball.y < 0) { if(ball.x > 500) { leftWon = true; slimeLeftScore++; } else { leftWon = false; slimeRightScore++; } endPoint() return true; } return false; } function updateFrame() { if(onePlayer) { slimeAI.move(false); // Move the right slime updateSlimeVelocitiesWithDoubleKeys(slimeLeft, KEY_A,KEY_LEFT, KEY_D,KEY_RIGHT, KEY_W,KEY_UP); updateSlimeVelocities(slimeRight,slimeAI.movement,slimeAI.jumpSet); } else { updateSlimeVelocitiesWithKeys(slimeLeft, KEY_A,KEY_D,KEY_W); updateSlimeVelocitiesWithKeys(slimeRight, KEY_LEFT,KEY_RIGHT,KEY_UP); } updateSlime(slimeLeft, 50 , 445); updateSlime(slimeRight, 555, 950); // Allows slimes to go accross the net //updateSlime(slimeLeft, 0, 1000); //updateSlime(slimeRight, 0, 1000); if(updateBall()) { return; } } function renderPoints(score, initialX, xDiff) { ctx.fillStyle = '#ff0'; var x = initialX; for(var i = 0; i < score; i++) { ctx.beginPath(); ctx.arc(x, 25, 12, 0, TWO_PI); ctx.fill(); x += xDiff; } ctx.strokeStyle = backTextColor; ctx.lineWidth = 2; x = initialX; for(var i = 0; i < WIN_AMOUNT; i++) { ctx.beginPath(); ctx.arc(x, 25, 12, 0, TWO_PI); ctx.stroke(); x += xDiff; } } // Rendering Functions function renderBackground() { if(legacyGraphics) { ctx.fillStyle=legacySkyColor; ctx.fillRect(0, 0, viewWidth, courtYPix); ctx.fillStyle = legacyGroundColor; } else { ctx.drawImage(backImage, 0, 0); ctx.fillStyle = newGroundColor; } ctx.fillRect(0, courtYPix, viewWidth, viewHeight - courtYPix); ctx.fillStyle='#fff' ctx.fillRect(viewWidth/2-2,7*viewHeight/10,4,viewHeight/10+5); // render scores renderPoints(slimeLeftScore, 30, 40); renderPoints(slimeRightScore, viewWidth - 30, -40); } // GAME CODE function renderGame() { if(updatesToPaint == 0) { console.log("ERROR: render called but not ready to paint"); } else { if(updatesToPaint > 1) { console.log("WARNING: render missed " + (updatesToPaint - 1) + " frame(s)"); } renderBackground(); ctx.fillStyle = '#000'; //ctx.font = "20px Georgia"; //ctx.fillText("Score: " + slimeLeftScore, 140, 20); //ctx.fillText("Score: " + slimeRightScore, viewWidth - 230, 20); ball.render(); slimeLeft.render(); slimeRight.render(); updatesToPaint = 0; } } function renderEndOfPoint() { var textWidth = ctx.measureText(endOfPointText).width; renderGame(); ctx.fillStyle = '#000'; ctx.fillText(endOfPointText, (viewWidth - textWidth)/2, courtYPix + (viewHeight - courtYPix)/2); } function gameIteration() { if(gameState == GAME_STATE_RUNNING) { updateCount++; if(slowMotion && (updateCount % 2) == 0) return; if(updatesToPaint > 0) { console.log("WARNING: updating frame before it was rendered"); } if(physicsLog > 0) { log("Frame"); log(" ball.x " + ball.x); log(" ball.y " + ball.y); log(" ball.vx " + ball.velocityX); log(" ball.vy " + ball.velocityY); physicsLog--; if(physicsLog == 0) { var logDom = document.createElement('pre'); logDom.innerHTML = logString; document.body.appendChild(logDom); } } updateFrame(); updatesToPaint++; if(updatesToPaint == 1) { requestAnimationFrame(renderGame); } } } function spaceKeyDown() { if(gameState == GAME_STATE_SHOW_WINNER) { if(onePlayer && nextSlimeIndex >= slimeAIs.length) { nextSlimeIndex = 0; toInitialMenu(); } else { start(onePlayer); } } } function endMatch() { gameState = GAME_STATE_SHOW_WINNER; clearInterval(gameIntervalObject); if(onePlayer) { if(leftWon) { nextSlimeIndex++; if(nextSlimeIndex >= slimeAIs.length) { menuDiv.innerHTML = '<div style="text-align:center;">' + '<h1 style="margin:50px 0 20px 0;">You beat everyone!</h1>' + "Press 'space' to do it all over again!" + '</div>'; } else { menuDiv.innerHTML = '<div style="text-align:center;">' + '<h1 style="margin:50px 0 20px 0;">You won!</h1>' + "Press 'space' to continue..." + '</div>'; } } else { nextSlimeIndex = 0; menuDiv.innerHTML = '<div style="text-align:center;">' + '<h1 style="margin:50px 0 20px 0;">You lost :(</h1>' + "Press 'space' to retry..." + '</div>'; } } else { menuDiv.innerHTML = '<div style="text-align:center;">' + '<h1 style="margin:50px 0 20px 0;">Player ' + (leftWon ? '1' : '2') + ' Wins!</h1>' + "Press 'space' for rematch..." + '</div>'; } menuDiv.style.display = 'block'; canvas.style.display = 'none'; } function startNextPoint() { initRound(leftWon); updatesToPaint = 0; updateCount = 0; gameState = GAME_STATE_RUNNING; } function endPoint() { if(slimeAI) { keysDown[KEY_UP] = false; keysDown[KEY_RIGHT] = false; keysDown[KEY_LEFT] = false; } if(slimeLeftScore >= WIN_AMOUNT) { endMatch(true); return; } if(slimeRightScore >= WIN_AMOUNT) { endMatch(false); return; } if(onePlayer) { if(leftWon) { endOfPointText = 'Nice, you got the point!'; } else { endOfPointText = slimeAI.name + ' scores!'; } } else { endOfPointText = 'Player ' + (leftWon ? '1':'2') + ' scores!'; } gameState = GAME_STATE_POINT_PAUSE; requestAnimationFrame(renderEndOfPoint); setTimeout(function () { if(gameState == GAME_STATE_POINT_PAUSE) { startNextPoint(); } }, 700); } function initRound(server) { ball.x = server ? 200 : 800; ball.y = 356; ball.velocityX = 0; ball.velocityY = 0; slimeLeft.x = 200; slimeLeft.y = 0; slimeLeft.velocityX = 0; slimeLeft.velocityY = 0; slimeRight.x = 800; slimeRight.y = 0; slimeRight.velocityX = 0; slimeRight.velocityY = 0; } function updateWindowSize(width,height) { viewWidth = width; viewHeight = height; console.log("ViewSize x: " + width + ", y: " + height); pixelsPerUnitX = width / gameWidth; pixelsPerUnitY = height / gameHeight; console.log("GAMESIZE x: " + gameWidth + ", y: " + gameHeight); console.log("PPU x: " + pixelsPerUnitX + ", y: " + pixelsPerUnitY); courtYPix = 4 * viewHeight / 5; } function setupView(view) { view.style.position = 'absolute'; view.style.left = '0'; view.style.top = '0'; } function bodyload() { var contentDiv = document.getElementById('GameContentDiv'); // Create Render objects canvas = document.createElement('canvas'); canvas.width = 750; canvas.height = 375; setupView(canvas,true); canvas.style.display = 'none'; ctx = canvas.getContext("2d"); ctx.font = "20px Georgia"; gameWidth = 1000; gameHeight = 1000; // Setup Render Data updateWindowSize(canvas.width,canvas.height); contentDiv.appendChild(canvas); // Create Menu Objects menuDiv = document.createElement('div'); setupView(menuDiv,false); menuDiv.style.width = '750px'; menuDiv.style.height = '375px'; menuDiv.style.background = "#ca6 url('sky2.jpg') no-repeat"; contentDiv.appendChild(menuDiv); // Create options menu div smallMenuDiv = document.createElement('div'); smallMenuDiv.style.position = 'absolute'; // Initialize Logging logString = ''; // Initialize Game Data nextSlimeIndex = 0; ball = newLegacyBall( 25,'#ff0'); slimeLeft = newLegacySlime(true,100,'#0f0'); slimeRight = newLegacySlime(false,100,'#f00'); var localSkyImage = new Image(); localSkyImage.src = 'sky2.jpg'; localSkyImage.onload = function() { backImages['sky'] = this; } var localCaveImage = new Image(); localCaveImage.src = 'cave.jpg'; localCaveImage.onload = function() { backImages['cave'] = this; } var sunsetImage = new Image(); sunsetImage.src = 'sunset.jpg'; sunsetImage.onload = function() { backImages['sunset'] = this; } var localBallImage = new Image(); localBallImage.src = 'vball.png'; localBallImage.onload = function() { ballImage = this; } greenSlimeImage = new Image(); greenSlimeImage.src = 'slime175green.png'; /* greenSlimeImage.onload = function() { slimeLeft.img = this; } */ redSlimeImage = new Image(); redSlimeImage.src = 'slime175red.png'; /* redSlimeImage.onload = function() { slimeRight.img = this; } */ toInitialMenu(); } // Menu Functions function start(startAsOnePlayer) { onePlayer = startAsOnePlayer; slimeLeftScore = 0; slimeRightScore = 0; slimeLeft.img = greenSlimeImage; if(onePlayer) { var slimeAIProps = slimeAIs[nextSlimeIndex]; slimeRight.color = slimeAIProps.color; legacySkyColor = slimeAIProps.legacySkyColor; backImage = backImages[slimeAIProps.backImageName]; backTextColor = slimeAIProps.backTextColor; legacyGroundColor= slimeAIProps.legacyGroundColor; legacyBallColor = slimeAIProps.legacyBallColor; newGroundColor = slimeAIProps.newGroundColor; slimeRight.img = null; slimeAI = newSlimeAI(false,slimeAIProps.name); slimeAIProps.initAI(slimeAI); } else { legacySkyColor = '#00f'; backImage = backImages['sky']; backTextColor = '#000'; legacyGroundColor= '#888'; legacyBallColor = '#fff'; newGroundColor = '#ca6'; slimeRight.img = redSlimeImage; slimeAI = null; } initRound(true); updatesToPaint = 0; updateCount = 0; loadOptions(); gameState = GAME_STATE_RUNNING renderBackground(); // clear the field canvas.style.display = 'block'; menuDiv.style.display = 'none'; gameIntervalObject = setInterval(gameIteration, 20); } function toInitialMenu() { menuDiv.innerHTML = '<div style="text-align:center;">' + '<h1 style="margin-top:30px;">Slime Volleyball</h1>' + '<span onclick="start(true)" class="btn" style="display:inline-block;margin:20px 30px 20px 30px;font-size:40px;">One Player</span>' + '<span onclick="start(false)" class="btn" style="display:inline-block;margin:20px 30px 20px 30px;font-size:40px;">Two Player</span>' + '<p>Originally written by Quin Pendragon and Daniel Wedge (http://oneslime.net)<br/>' + 'Rewritten by Jonathan Marler</p>' '</div>'; } function loadOptions() { legacyGraphics = document.getElementById('LegacyGraphics').checked; slowMotion = document.getElementById('SlowMotion').checked; if(document.getElementById('PhysicsLog').checked) { physicsLog = 120; } else { physicsLog = 0; } } function showOptions() { if(gameState == GAME_STATE_RUNNING) { gameState = GAME_STATE_MENU_PAUSE; } else if(gameState == GAME_STATE_POINT_PAUSE) { gameState = GAME_STATE_MENU_PAUSE_BETWEEN_POINTS; } var div = document.getElementById('OptionsDiv'); div.style.display = 'block'; } function hideOptions() { var div = document.getElementById('OptionsDiv'); div.style.display = 'none'; if(gameState == GAME_STATE_MENU_PAUSE) { updateCount = 0; gameState = GAME_STATE_RUNNING; } else if(gameState == GAME_STATE_MENU_PAUSE_BETWEEN_POINTS) { startNextPoint(); } loadOptions(); } </script> </head><body onload="bodyload()" style="text-align:center;"> <div id="ContentDiv" style="position:relative;margin:15px auto;width:750px;"> <div id="GameContentDiv"></div> <div id="OptionsDiv" style="position:relative;width:400px;height:360px;margin:0 auto;background:#eee;display:none;border:3px solid #ca6;"> <h2>Options</h2><hr/><br/> <span class="SmallButton" onclick="hideOptions()" style="position:absolute;top:5px;right:5px;font-weight:bold;">close</span> <div style="text-align:left;margin:10px;"> <input id="LegacyGraphics" type="checkbox">Legacy Graphics</input> <br/><br/> <h3>Debug Options</h3><br/> <input id="PhysicsLog" type="checkbox">Enable Physics Log</input> <br/><br/> <h3>Cheats</h3><br/> <input id="SlowMotion" type="checkbox">Slow Motion</input> </div> </div> <span class="SmallButton" style="position:absolute;top:350px;left:10px;" onclick="showOptions()"> Options </span> </div> </body></html>