diff --git a/src/client/scenes/slime_volleyball.coffee b/src/client/scenes/slime_volleyball.coffee index 8e47caa..aaedebb 100644 --- a/src/client/scenes/slime_volleyball.coffee +++ b/src/client/scenes/slime_volleyball.coffee @@ -13,6 +13,14 @@ class SlimeVolleyball extends Scene @buttons = { # create a back button back: new Button(@width/2-Constants.BACK_BTN_WIDTH/2, Constants.SCOREBOARD_PADDING, Constants.BACK_BTN_WIDTH, Constants.BACK_BTN_HEIGHT, loader.getAsset('return'), loader.getAsset('return'), this) } + + # Set the difficulty + try + @percent = document.cookie.match(/AI_DIFFICULTY=(\d\.\d*)/i)[1] + catch e + @percent = Constants.AI_DIFFICULTY + finally + Constants.AI_DIFFICULTY = @percent # make sure it is set @sprites = [] @sprites.push(@bg, @world.pole, @world.p1, @world.p2, @world.ball, @p1Scoreboard, @p2Scoreboard, @buttons.back) @@ -59,16 +67,52 @@ class SlimeVolleyball extends Scene changed moveCPU: -> # implement a basic AI - if @ball.x > @pole.x && @ball.y < 200 && @ball.y > 150 && @p2.velocity.y == 0 - @p2.velocity.y = -8 - if @ball.x > @pole.x - @p1.width && @ball.x < @p2.x - @p2.x -= (Constants.MOVEMENT_SPEED*.75) + (Constants.MOVEMENT_SPEED*Constants.AI_DIFFICULTY) - if @ball.x > @pole.x - @p1.width && @ball.x + @ball.width + (@ball.velocity.x * Constants.AI_DIFFICULTY) > @p2.x + @p2.width && @ball.x + @ball.width < @width - @p2.x += (Constants.MOVEMENT_SPEED*.75) + (Constants.MOVEMENT_SPEED*Constants.AI_DIFFICULTY) - else if @ball.x > @pole.x - @p1.width && @ball.x + @ball.width + (@ball.velocity.x * Constants.AI_DIFFICULTY) > @p2.x + @p2.width && @ball.x + @ball.width >= @width - @p2.x -= (Constants.MOVEMENT_SPEED*.75) + (Constants.MOVEMENT_SPEED*Constants.AI_DIFFICULTY) - if @ball.x + @ball.radius > @p2.x + 30 && @ball.x + @ball.radius < @p2.x + 34 - @p2.x += (Constants.MOVEMENT_SPEED*.75) + (Constants.MOVEMENT_SPEED*Constants.AI_DIFFICULTY) + return if @freezeGame + # Predict where the ball is going to end up + # Clone the ball obj + ball = { + x: @ball.x + y: @ball.y + velocity: { + x: @ball.velocity.x + y: @ball.velocity.y + } + acceleration: { + x: @ball.acceleration.x + y: @ball.acceleration.y + } + } + floor = @height - Constants.BOTTOM + while ball.y < floor - @p2.height # predicting the position where will be at slime height + # switch vel if hits wall + ball.velocity.x *= -1 if ball.x > @width || ball.x < 0 + ball.x += ball.velocity.x * Constants.FPS_RATIO + ball.y += ball.velocity.y * Constants.FPS_RATIO + ball.velocity.y += ball.acceleration.y * Constants.FPS_RATIO + + p2Pos = @p2.x + @p2.width / 2 - 14 + pastP1 = @ball.x > @p1.x + @p1.width / 2 + @ball.radius + pastPole = ball.x > @pole.x + ballPos = @ball.x + @ball.radius + ballLand = ball.x + @ball.radius + + # Angle between current pos, and land + ballAngle = Math.atan2( ballLand - ballPos, @ball.height ) + + # Where he wants to be to hit it (based on the angle of the ball and distance from pole) + # More weight is on the angle than the distance + # the randomness makes him stupider + sweetSpot = p2Pos - Constants.AI_DIFFICULTY * ( 1 - ( 1 / Constants.AI_DIFFICULTY ) * Math.random() ) * .8 * ( 2 * ( ( ( p2Pos - @pole.x ) / ( @width / 2 ) ) + 8 * ( 1.57 - Math.abs ballAngle ) ) ) + + # jump only if angle is steep enough, or ball will land past + if ( Math.abs( ballPos - sweetSpot ) <= 5 || ballPos - sweetSpot > 5 ) && @ball.y < 200 && @ball.y > 100 && @p2.velocity.y == 0 && ( ( ballAngle > -.8 && ballAngle < 0.8 ) ) + @p2.velocity.y = -8 # jump + # ball will pass p2 + if ballLand > sweetSpot + 5 # have him shoot at a lower angle if it's less steep + @p2.x += (Constants.MOVEMENT_SPEED*.55) + (Constants.MOVEMENT_SPEED*Constants.AI_DIFFICULTY) + # Ball past 1 and will land past net OR ball heading toward p1 from our side + else if ( ( pastP1 && pastPole ) || ( @ball.velocity.x < 0 && @ball.x > @pole.x ) ) && ballLand < sweetSpot - 5 + @p2.x -= (Constants.MOVEMENT_SPEED*.55) + (Constants.MOVEMENT_SPEED*Constants.AI_DIFFICULTY) draw: -> # draw everything! @@ -118,6 +162,7 @@ class SlimeVolleyball extends Scene setTimeout(( => # start game in 1 second @world.reset(winner) @displayMsg = null + @stepLen = Constants.TICK_DURATION @freezeGame = false ), 1000) @@ -126,8 +171,10 @@ class SlimeVolleyball extends Scene step: (timestamp) -> this.next() # constantly demand ~60fps return this.draw() if @freezeGame # don't change anything! + # apply input and then step - @world.step() # step physics + @world.step( @stepLen ) # step physics + @stepLen = null # end game when ball hits ground if @world.ball.y + @world.ball.height >= @world.height-Constants.BOTTOM winner = if @world.ball.x+@world.ball.radius > @width/2 then @world.p1 else @world.p2 diff --git a/src/shared/constants.coffee b/src/shared/constants.coffee index a229344..6aafbb9 100644 --- a/src/shared/constants.coffee +++ b/src/shared/constants.coffee @@ -26,7 +26,7 @@ Constants = MOVEMENT_SPEED: 4 JUMP_SPEED: 12 SLIME_START_HEIGHT: 91 - AI_DIFFICULTY: 0.25 + AI_DIFFICULTY: 0.35 MSG_FONT: 'Courier, monospace, sans-serif' FPS_RATIO: 24 / 16 # in relation to what the initial speeds were set for (16ms) TICK_DURATION: 24 #ms diff --git a/src/shared/world.coffee b/src/shared/world.coffee index 6634490..f9a6c11 100644 --- a/src/shared/world.coffee +++ b/src/shared/world.coffee @@ -76,13 +76,14 @@ class World @lastStep = now unless dontIncrementClock # automatically break up longer steps into a series of shorter steps - if interval >= 1.3 * tick + if interval >= 1.3 * tick && @deterministic while interval > 0 newInterval = if interval >= 1.3 * tick then tick else interval this.step(newInterval, dontIncrementClock) interval -= newInterval return # don't continue stepping - else interval = tick + else if @deterministic + interval = tick @numFrames = interval / tick