Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

- Multiplayer v2

commit 5edd20d0f618af68536c8d1cf8d693a9fa8a9ecf 1 parent 61ee7d4
Austin Hallock austinhallock authored joevennix committed
66 build/server/encryption.js
View
@@ -0,0 +1,66 @@
+/*
+ * clay-encryption
+ *
+ * Quick and simple library for encrypting
+ * and decrypting objects to be passed to clay.io
+ */
+
+// dependencies (note that jwt-simple requires crypto)
+var jwt = require( '/usr/local/lib/node_modules/jwt-simple' );
+
+Clay = ( function()
+{
+ // The associated user
+ Clay.identifier = '';
+
+ // The developer's secret key
+ Clay.secretKey = '';
+
+ /**
+ * @param {String} identifier - unique identifier for player
+ * @param {String} secretKey - secret key for this game
+ */
+ function Clay( identifier, secretKey )
+ {
+ if( typeof identifier != 'undefined' )
+ Clay.identifier = identifier
+
+ if( typeof secretKey != 'undefined' )
+ Clay.secretKey = secretKey
+ }
+
+ /**
+ * Stores the user's unique identifier
+ * @param {String} identifier
+ */
+ Clay.prototype.storeIdentifier = function( identifier )
+ {
+ Clay.identifier = identifier;
+ }
+
+ /**
+ * Encodes jwt
+ * @param {Object} options
+ */
+ Clay.prototype.encode = function( options )
+ {
+ // Add the necessary options
+ options.identifier = Clay.identifier;
+ options.timestamp = Math.round( new Date().getTime() / 1000 )
+
+ return jwt.encode( options, Clay.secretKey );
+ }
+
+ /**
+ * Decodes jwt
+ * @param {String} JWT encoded string
+ */
+ Clay.prototype.decode = function( options )
+ {
+ return jwt.decode( options, Clay.secretKey );
+ }
+
+ return Clay;
+} )();
+
+module.exports = Clay
BIN  clay_big.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  icon_128.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
134 index.html
View
@@ -1,30 +1,118 @@
<html>
<head>
- <meta name="apple-mobile-web-app-capable" content="yes" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0; ">
- <link rel="apple-touch-icon" href="/assets/images/icon.png" />
- <link rel='shortcut icon' href='/assets/images/favicon.ico' />
+ <meta name="apple-mobile-web-app-capable" content="yes" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="chrome=1, IE=edge">
+ <link rel="apple-touch-icon" href="/assets/images/icon.png" />
+ <link rel='shortcut icon' href='/assets/images/favicon.ico' />
- <script type="text/javascript" src="build/client/game.js"></script>
- <script type='text/javascript'>
- Clay = {};
- Clay.gameKey = 'slime'
- Clay.ready = function( fn ) {
- Clay.readyFunction = fn;
- };
- ( function() {
- var clay = document.createElement("script");
- clay.src = "http://clay.io/api/api.js";
- var tag = document.getElementsByTagName("script")[0];
- tag.parentNode.insertBefore(clay, tag);
- } )();
- </script>
- <style>
- body, html, * {margin:0;padding:0;font-size:8px;}
-
- </style>
+ <script type="text/javascript" src="build/client/game.js"></script>
+ <script type='text/javascript'>
+ Clay = {};
+ Clay.gameKey = 'slime'
+ Clay.ready = function( fn ) {
+ Clay.readyFunction = fn;
+ };
+ ( function() {
+ var clay = document.createElement("script");
+ clay.src = "http://clay.io/api/api.js";
+ var tag = document.getElementsByTagName("script")[0];
+ tag.parentNode.insertBefore(clay, tag);
+ } )();
+ </script>
+ <style>
+ body, html {
+ margin:0;
+ padding:0;
+ }
+ body {
+ background: #9FFF23;
+ font: 12px "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, "Lucida Grande", sans-serif;
+ }
+ #center {
+ margin: 0 auto;
+ text-align: center;
+ width: 480px;
+ }
+ #please-rotate {
+ width: 100%;
+ height: 100%;
+ position: fixed;
+ z-index: 5;
+ background: #fff;
+ display: none;
+ text-align: center;
+ font-size: 16px;
+ }
+ .info {
+ display: none;
+ }
+ @media only screen and (min-width: 800px) {
+ #center {
+ padding-top: 15px;
+ }
+ .info {
+ display: block;
+ text-align: left;
+ margin-top: 5px;
+ border: 1px solid rgba( 0, 0, 0, 0.3 );
+ }
+ .info .social {
+ background: rgba( 0, 0, 0, 0.3 );
+ color: #fff;
+ }
+ .info .social a {
+ display: block;
+ float: left;
+ padding: 5px;
+ background: rgba( 255, 255, 255, 0.1 );
+ color: #fff;
+ text-decoration: none;
+ text-align: center;
+ margin-right: 8px;
+ width: 103px;
+ }
+ .info .social a:hover {
+ background: rgba( 255, 255, 255, 0.3 );
+ }
+ .info .social a.last {
+ margin-right: 0;
+ }
+ .info .instructions {
+ padding: 5px;
+ }
+ .clear {
+ clear: both;
+ }
+ }
+ </style>
</head>
<body style="margin:0px;">
- <canvas id="canvas" width="480px" height="268px"></canvas>
+ <div id='please-rotate'>
+ Please rotate your device to landscape mode.
+ </div>
+ <div id='center'>
+ <canvas id="canvas" width="480px" height="268px"></canvas>
+ <div class='info'>
+ <div class='social'>
+ <a href='javascript: void( 0 );' onclick='Social.facebook();'>
+ Post to Facebook
+ </a>
+ <a href='javascript: void( 0 );' onclick='Social.tweet();'>
+ Post to Twitter
+ </a>
+ <a href='javascript: void( 0 );' onclick='new Clay.Screenshot();'>
+ Take a screenshot
+ </a>
+ <a href='javascript: void( 0 );' onclick='Clay.Ratings();' class='last'>
+ Rate this game
+ </a>
+ <div class='clear'></div>
+ </div>
+ <div class='instructions'>Instructions: <br />
+ Use the arrow keys to move (or WASD), hit the ball over the net.
+ </div>
+ </div>
+ </div>
</body>
</html>
17 manifest.json
View
@@ -0,0 +1,17 @@
+{
+ "name": "Slime Volley",
+ "description": "Volleyball...between two slimes.",
+ "version": "0.1.8",
+ "app": {
+ "launch": {
+ "local_path": "index.html"
+ }
+ },
+ "icons": {
+ "128": "icon_128.png"
+ },
+ "permissions": [
+ "unlimitedStorage",
+ "notifications"
+ ]
+}
BIN  slimey.zip
View
Binary file not shown
28 src/client/main.coffee
View
@@ -1,3 +1,21 @@
+# Scroll past URL bar
+scrollTop = ->
+ doScrollTop = setInterval(->
+ if document.body
+ clearInterval doScrollTop
+ scrollTo 0, 1
+ pageYOffset = 0
+ scrollTo 0, (if (pageYOffset is document.body.scrollTop) then 1 else 0)
+
+ # Check width
+ if window.innerWidth < 350
+ document.getElementById( 'please-rotate' ).style.display = 'block'
+ else
+ document.getElementById( 'please-rotate' ).style.display = 'none'
+ , 200)
+window.addEventListener 'orientationchange', ->
+ scrollTop()
+
# FIXME run the game when the dom loads
window.addEventListener 'load', ->
pixelRatio = window.devicePixelRatio || 1
@@ -8,14 +26,8 @@ window.addEventListener 'load', ->
pageFill = document.createElement("div")
pageFill.style.height = (window.innerHeight - document.body.clientHeight + 100) + "px"
document.getElementsByTagName("body")[0].appendChild pageFill
- # Scroll past URL bar
- doScrollTop = setInterval(->
- if document.body and not ((pageYOffset or document.body.scrollTop) > 20)
- clearInterval doScrollTop
- scrollTo 0, 1
- pageYOffset = 0
- scrollTo 0, (if (pageYOffset is document.body.scrollTop) then 1 else 0)
- , 200)
+
+ scrollTop()
Globals.Manager.canvas = canvas
Globals.Manager.ctx = Globals.Manager.canvas.getContext('2d')
1  src/client/manifest.js
View
@@ -15,6 +15,7 @@
//= require '../shared/ball'
//= require '../shared/slime'
//= require 'scoreboard'
+//= require 'social'
//= require '../shared/world'
//= require 'scenes/loading_scene'
15 src/client/scenes/menu_scene.coffee
View
@@ -10,6 +10,12 @@ class MenuScene extends Scene
@bg = new StretchySprite(0, 0, @width, @height, 1, 1, loader.getAsset('menu_bg'))
@logo = new Sprite(@center.x-128, @center.y-155, 256, 256, loader.getAsset('logo'))
@logo.velocity = 0
+ Clay.ready =>
+ @clayRooms = new Clay.Rooms (roomInfo) =>
+ networkGame = new NetworkSlimeVolleyball()
+ networkGame.roomID = roomInfo.id
+ networkGame.rooms = roomInfo.instance
+ Globals.Manager.pushScene networkGame
dy = @center.y + 30
btnWidth = 234
btnHeight = 44
@@ -44,15 +50,12 @@ class MenuScene extends Scene
# delegate callback when a button is pressed
buttonPressed: (btn) ->
if btn == @buttons['leaderboards']
- new Clay.Leaderboard(1).show();
+ new Clay.Leaderboard( { id: 6 } ).show();
+ # TODO: multiplayer LB
else if btn == @buttons['onePlayer']
# new volleyball game
Globals.Manager.pushScene new SlimeVolleyball()
else if btn == @buttons['options']
Globals.Manager.pushScene new OptionsScene()
else if btn == @buttons['wifi']
- r = new Clay.Rooms (roomInfo) ->
- networkGame = new NetworkSlimeVolleyball()
- networkGame.roomID = roomInfo.id
- Globals.Manager.pushScene networkGame
- r.show()
+ @clayRooms.show()
154 src/client/scenes/network_slime_volleyball.coffee
View
@@ -2,6 +2,7 @@
class NetworkSlimeVolleyball extends SlimeVolleyball
init: -> # load socket.io asynchronously
@world = new World(@width, @height, new InputSnapshot())
+
super(true)
@freezeGame = true
@displayMsg = 'Loading...'
@@ -11,11 +12,13 @@ class NetworkSlimeVolleyball extends SlimeVolleyball
@world.deterministic = true # necessary for sync
@msAhead = Constants.TARGET_LATENCY
@sentWin = false
- @loopCount = 0
+ @stepCallback = => this.step()
+ # @loopCount = 0
+
# initialize socket.io connection to server
@socket.disconnect() && @socket = null if @socket
- @socket = io.connect('/')
+ @socket = io.connect('http://clay.io:845', { 'force new connection':true, 'reconnect':false } )
@socket.on 'connect', =>
@displayMsg = 'Connected. Waiting for opponent...'
this.joinRoom()
@@ -30,54 +33,78 @@ class NetworkSlimeVolleyball extends SlimeVolleyball
@sentWin = false
this.start()
@socket.on 'gameFrame', (data) =>
- # use this to calculate latency
+ # To make FPS the same as server
msAhead = @world.clock - data.state.clock
- @msAhead = 0.8*@msAhead + 0.2*msAhead
- @receivedFrames.push(data)
+ @msAhead = msAhead
+ # Update positions and inputs of all game objects to match the server
+ @receivedFrames.push( data )
+ # Called if they won the game (not just round)
+ @socket.on 'gameWin', (jwt) =>
+ # Clay Leaderboard
+ console.log jwt
+ lb = new Clay.Leaderboard( { id: 1 } )
+ lb.post jwt # increment win total by 1 (encrypted on server-side)
@socket.on 'roundEnd', (didWin) =>
- @freezeGame = true
- @world.ball.y = @height - Constants.BOTTOM - 2*@world.ball.radius
- @lastWinner = if didWin then @world.p1 else @world.p2
- if didWin
- @displayMsg = @winMsgs[Helpers.rand(@winMsgs.length-2)]
- @world.p1.score += 1
- else
- @displayMsg = @failMsgs[Helpers.rand(@winMsgs.length-2)]
- @world.p2.score += 1
- if @world.p1.score >= Constants.WIN_SCORE
- #@socket.disconnect()
- @displayMsg = 'You WIN!!!'
+ endRound = =>
+ @freezeGame = true
+ @world.ball.y = @height - Constants.BOTTOM - 2*@world.ball.radius
+ @lastWinner = if didWin then @world.p1 else @world.p2
+ # Clear the game interval
+ if didWin
+ @displayMsg = @winMsgs[Helpers.rand(@winMsgs.length-2)]
+ @world.p1.score += 1
+ else
+ @displayMsg = @failMsgs[Helpers.rand(@winMsgs.length-2)]
+ @world.p2.score += 1
+ if @world.p1.score >= Constants.WIN_SCORE || @world.p2.score >= Constants.WIN_SCORE
+ if @world.p1.score >= Constants.WIN_SCORE
+ @displayMsg = 'You WIN!!!'
+ else
+ @displayMsg = 'You LOSE.'
+ @displayMsg += 'New game starting in 3 seconds'
+
+
+ # reset the game scores to 0
+ @world.p1.score = 0
+ @world.p2.score = 0
+ # New game initiated by server, starts in 3 seconds
+
+ @stop()
+ setTimeout endRound, Constants.TARGET_LATENCY #give a little time so it doesn't look like the ball just drops
+ @socket.on 'gameDestroy', (winner) =>
+ if @socket
@freezeGame = true
@socket = null
+ @displayMsg = 'Lost connection to opponent.'
+ @stop()
setTimeout ( =>
- Globals.Manager.popScene()
- ), 1000
- else if @world.p2.score >= Constants.WIN_SCORE
- #@socket.disconnect()
- @displayMsg = 'You LOSE.'
- @freezeGame = true
+ Globals.Manager.popScene() if Globals.Manager.sceneStack[Globals.Manager.sceneStack.length-2]
+ # Leave the clay.io room
+ @rooms.leaveRoom()
+ ), 2000
+ @socket.on 'disconnect', =>
+ if @socket
+ @freezeGame = true
@socket = null
+ @displayMsg = 'Lost connection to opponent.'
+ @stop()
setTimeout ( =>
- Globals.Manager.popScene()
- ), 1000
- @socket.on 'gameDestroy', (winner) =>
- @freezeGame = true
- @socket = null
- @displayMsg = 'Lost connection to opponent.'
- @socket.on 'disconnect', =>
- @freezeGame = true
- @socket = null
- @displayMsg = 'Lost connection to opponent.'
+ Globals.Manager.popScene() if Globals.Manager.sceneStack[Globals.Manager.sceneStack.length-2]
+ # Leave the clay.io room
+ @rooms.leaveRoom()
+ ), 2000
@socketInitialized = true
joinRoom: ->
- @socket.emit('joinRoom', @roomID) if @roomID
+ obj = { roomID: @roomID, playerID: Clay.player.identifier }
+ @socket.emit('joinRoom', obj) if @roomID
start: -> # prebuffer the first Constants.FRAME_DELAY frames
for i in [0...Constants.FRAME_DELAY]
@world.step(Constants.TICK_DURATION, true)
- @framebuffer.push(@world.getState())
- super()
+ # @framebuffer.push(@world.getState())
+ this.step()
+ @gameInterval = setInterval(@stepCallback, Constants.TICK_DURATION)
draw: ->
return unless @ctx
@@ -88,10 +115,10 @@ class NetworkSlimeVolleyball extends SlimeVolleyball
@bg.draw(@ctx)
@world.p1.draw(@ctx, frame.p1.x, frame.p1.y)
@world.p2.draw(@ctx, frame.p2.x, frame.p2.y)
+ @world.ball.draw(@ctx, frame.ball.x, frame.ball.y)
@world.pole.draw(@ctx)
@p1Scoreboard.draw(@ctx)
@p2Scoreboard.draw(@ctx)
- @world.ball.draw(@ctx, frame.ball.x, frame.ball.y)
@buttons['back'].draw(@ctx)
# draw displayMsg, if any
if @displayMsg
@@ -104,50 +131,45 @@ class NetworkSlimeVolleyball extends SlimeVolleyball
@ctx.font = 'bold 11px ' + Constants.MSG_FONT
@ctx.fillText(msgs[1], @width/2, 110)
+ ###
throttleFPS: ->
if @msAhead > Constants.TARGET_LATENCY # throttle fps to sync with the server's clock
if @loopCount % 10 == 0 # drop every tenth frame
@stepLen = Constants.TICK_DURATION # we have to explicitly set step size, otherwise the physics engine will just compensate by calculating a larger step
return
-
- handleInput: ->
- changed = this.inputChanged() # send update to server that input has changed
- if changed
- frame =
- input:
- p1: changed
- state:
- p1: @world.p1.getState()
- ball: @world.ball.getState()
- clock: @world.clock
- @socket.emit('input', frame)
- @world.injectFrame(frame)
+ ###
- handleWin: (winner) ->
- unless @sentWin
- @socket.emit('gameEnd', winner)
- @sentWin = true
-
+ handleInput: ->
+ if !@freezeGame
+ changed = this.inputChanged() # send update to server that input has changed
+ if changed
+ frame =
+ input:
+ p1: changed
+ @socket.emit('input', frame) # don't need to set now, we'll take what the server gives us
+
+ stop: ->
+ this.draw()
+ clearInterval @gameInterval
step: (timestamp) ->
- this.next()
- @loopCount++
+ # this.next() setInterval so we have a somewhat unified fps
+ # @loopCount++
if @receivedFrames
while @receivedFrames.length > 0
f = @receivedFrames.shift()
@world.injectFrame(f)
+ this.handleInput()
if @freezeGame || !@socketInitialized # freeze everything!
- @gameStateBuffer.push(@world.getState()) if @gameStateBuffer
+ # @gameStateBuffer.push(@world.getState()) if @gameStateBuffer
this.draw()
return
- this.handleInput()
- this.throttleFPS()
- if @world.ball.y + @world.ball.height >= @world.height-Constants.BOTTOM #
- winner = if @world.ball.x+@world.ball.radius > @width/2 then 'p1' else 'p2'
- this.handleWin(winner)
- @world.step(@stepLen) # step physics
- @stepLen = null # only drop ONE frame
- @framebuffer.push(@world.getState()) # save in buffer
+
+ # this.throttleFPS()
+
+ @world.step() #@stepLen) # step physics
+ # @stepLen = null # only drop ONE frame
+ # @framebuffer.push(@world.getState()) # save in buffer
this.draw() # we overrode this to draw frame at front of buffer
11 src/client/scenes/slime_volleyball.coffee
View
@@ -88,7 +88,14 @@ class SlimeVolleyball extends Scene
@world.ball.y = @height-Constants.BOTTOM-@world.ball.height
@world.ball.velocity = { x: 0, y: 0 }
@world.ball.falling = false
- msgList = if winner == @world.p1 then @winMsgs else @failMsgs
+ if winner == @world.p1
+ msgList = @winMsgs
+ if winner.score >= Constants.WIN_SCORE # p1 won the game
+ # Clay Leaderboard - TODO: JWT encryption
+ lb = new Clay.Leaderboard( 5 )
+ lb.post 1 # increment win total by 1
+ else
+ msgList = @failMsgs
msgIdx = if winner.score < Constants.WIN_SCORE then Helpers.rand(msgList.length-2) else msgList.length-1
@displayMsg = msgList[msgIdx]
if winner.score < Constants.WIN_SCORE
@@ -100,7 +107,7 @@ class SlimeVolleyball extends Scene
), 1000)
- # main "loop" iteration
+ # main "loop" iteration
step: (timestamp) ->
this.next() # constantly demand ~60fps
return this.draw() if @freezeGame # don't change anything!
8 src/client/social.coffee
View
@@ -0,0 +1,8 @@
+class Social
+ @tweet: ->
+ twitter = new Clay.Twitter()
+ twitter.post "Check out this game, Slime Volley, on clay.io - http://slime.clay.io"
+ @facebook: ->
+ facebook = new Clay.Facebook()
+ facebook.post "Check out this game, Slime Volley, on clay.io - http://slime.clay.io"
+
110 src/server/game_runner.coffee
View
@@ -13,10 +13,10 @@ class GameRunner
@world = new World(@width, @height, new InputSnapshot())
@running = false
@loopCount = 0
- @stepCallback = => this.step()
+ @stepCallback = => this.step( Constants.TICK_DURATION )
- next: -> # iterate game "loop"
- @lastTimeout = setTimeout(@stepCallback, Constants.TICK_DURATION)
+ #next: -> # iterate game "loop"
+ # @lastTimeout = setTimeout(@stepCallback, Constants.TICK_DURATION)
start: -> # send gameStart signal
@running = true
@@ -24,27 +24,49 @@ class GameRunner
this.sendFrame('gameInit')
console.log '-- GAME INIT ---'
this.sendFrame()
- @lastTimeout = setTimeout(( =>
+ @lastTimeout = setTimeout =>
@freezeGame = false
this.sendFrame('gameStart')
- this.step()
- ), 1000) # start game in 1 second
-
+ # start the game and game interval
+ this.step()
+ @gameInterval = setInterval(@stepCallback, Constants.TICK_DURATION)
+ , 1000 # start game in 1 second
handleWin: (winner) ->
@freezeGame = true
+ @stop()
@world.ball.y = @height-Constants.BOTTOM-@world.ball.height
@world.ball.velocity = { x: 0, y: 0 }
@world.ball.falling = false
- p1Won = winner == 'p1' # || @ball.x+@ball.radius > @width/2
+
+ p1Won = winner == 'p1' || @world.ball.x+@world.ball.radius > @width/2
@world.reset(if p1Won then @world.p1 else @world.p2)
@room.p1.socket.emit('roundEnd', p1Won) if @room.p1
@room.p2.socket.emit('roundEnd', !p1Won) if @room.p2
+
+ if p1Won
+ @world.p1.score++
+ else
+ @world.p2.score++
+
+ # check if the game is over and handle the leaderboard
+ if @world.p1.score >= Constants.WIN_SCORE || @world.p2.score >= Constants.WIN_SCORE
+ if @world.p1.score >= Constants.WIN_SCORE
+ # Increment leaderboard by 1 for player 1
+ jwt = @room.p1.clay.encode { score: 1 } if @room.p2
+ @room.p1.socket.emit('gameWin', jwt) if @room.p1
+ else
+ # Increment leaderboard by 1 for player 2
+ jwt = @room.p2.clay.encode { score: 1 } if @room.p2
+ @room.p2.socket.emit('gameWin', jwt) if @room.p2
+
+
# start game again in one second
- @lastTimeout = setTimeout(( =>
+ @lastTimeout = setTimeout =>
@freezeGame = false
this.sendFrame('gameStart')
- this.step()
- ), 1000)
+ this.step( Constants.TICK_DURATION )
+ @gameInterval = setInterval(@stepCallback, Constants.TICK_DURATION)
+ , 3000
step: ->
return if @freezeGame
@@ -53,13 +75,18 @@ class GameRunner
this.handleWin()
return
@loopCount++
- #this.next()
- #@world.step()
- #this.sendFrame() if @loopCount % 25 == 0
- @newInput = null
+ #this.next() # this runs on an inverval now to make it more of a concrete time
+
+ # The server doesn't iterate at exactly the frame rate we want, but on average it does
+ # So we can do this (setting the tick)
+ @world.step( Constants.TICK_DURATION )
+ # For position/state verification
+ this.sendFrame() if @loopCount % 10 == 0
+ # @newInput = null
stop: ->
clearTimeout @lastTimeout
+ clearInterval @gameInterval
invertFrameX: (frame) ->
if frame.state
@@ -70,30 +97,51 @@ class GameRunner
ref = frame.state.p1
frame.state.p1 = frame.state.p2
frame.state.p2 = ref
+
if frame.input
ref = frame.input.p1
frame.input.p1 = frame.input.p2
frame.input.p2 = ref
for obj in [frame.input.p1, frame.input.p2]
- if obj
- ref = obj.left
- obj.left = obj.right
- obj.right = ref
+ @invertInput obj
+
frame
+
+ # inverts input for 1 player
+ invertInput: (obj) ->
+ if obj
+ ref = obj.left
+ obj.left = obj.right
+ obj.right = ref
+ obj
#game.injectFrame(input, this == @room.p2)
+ # this just handles input now
injectFrame: (frame, isP2) ->
- frame.state.p2 = frame.state.ball = null # don't accept opponent state
- this.invertFrameX(frame) if isP2
- #@world.injectFrame(frame)
- outgoingFrame = {
- state: frame.state,
- input: frame.input
- } # prevent 'prev' and next refs from sneaking into our json
- #console.log 'INJECTING INPUT: framesAhead = '+(frame.state.clock-@world.clock)+':'
- #console.log frame.input
- @room.p1.socket.emit('gameFrame', outgoingFrame) if isP2 && @room.p1
- @room.p2.socket.emit('gameFrame', this.invertFrameX(outgoingFrame)) if !isP2 && @room.p2
+ return if @freezeGame
+ inputTypes = ['left', 'right', 'up']
+ inputState = {}
+ # Input received from p1
+ if !isP2 && @room.p1
+ # Have to set this inputs individually
+ # Since the client only passes the inputs that were changed
+ for type in inputTypes
+ if typeof frame.input.p1[type] != 'undefined'
+ inputState[type] = frame.input.p1[type] #update p1's input
+ # Save it
+ @world.input.setState inputState, 0
+
+ # Input received from p2
+ if isP2 && @room.p2
+ @invertFrameX frame
+ for type in inputTypes
+ if typeof frame.input.p2[type] != 'undefined'
+ inputState[type] = frame.input.p2[type] #update p2's input
+ # Save it
+ @world.input.setState inputState, 1
+
+ # Send back to clients
+ @sendFrame()
sendFrame: (notificationName) -> # take a snapshot of game state and send it to our clients
notificationName ||= 'gameFrame'
@@ -102,5 +150,5 @@ class GameRunner
# invert x of state to send to client 2
this.invertFrameX(frame)
@room.p2.socket.emit(notificationName, frame) if @room.p2
-
+
module.exports = GameRunner
19 src/server/player.coffee
View
@@ -3,10 +3,13 @@ Room = require('./room')
class Player
constructor: (@socket) ->
@room = null
- @socket.on 'joinRoom', (roomID) => this.joinRoom(roomID)
+ @socket.on 'joinRoom', (obj) => this.joinRoom(obj)
@socket.on 'input', (frame) => this.receiveInput(frame)
- @socket.on 'gameEnd', (frame) => this.receiveGameEnd(frame)
+ # @socket.on 'gameEnd', (frame) => this.receiveGameEnd(frame)
@socket.on 'disconnect', => this.didDisconnect()
+
+ # Accessor for encoding JWT objects
+ @clay = null
receiveInput: (frame) ->
@room.game.injectFrame(frame, this == @room.p2) if @room
@@ -14,13 +17,21 @@ class Player
receiveGameEnd: (winner) ->
@room.game.handleWin(winner) if @room && this == @room.p1
- joinRoom: (roomID) ->
+ joinRoom: (obj) ->
+ roomID = obj.roomID
+ playerID = obj.playerID
+
+ # Store the unique identifier for this player
+ secret = 'SECRETKEYHERE' # Secret key for this game
+ @clay = new Clay( playerID, secret )
+
@room.stopGame if @room
@room = Room.AllRooms[roomID] || new Room(2)
@room.addPlayer(this)
+ @room.id = roomID
Room.AllRooms[roomID] = @room
didDisconnect: ->
@room.stopGame() if @room
-module.exports = Player
+module.exports = Player
4 src/server/room.coffee
View
@@ -8,6 +8,7 @@ class Room
@players.push(@p1) if @p1
@players.push(@p2) if @p2
@open = this.startGame() # will fail if not enough players
+ @id = 0 # roomID
addPlayer: (p) ->
console.log '-- ADDING PLAYER TO ROOM --'
@@ -17,6 +18,9 @@ class Room
p.room = this
p.socket.on 'disconnect', => # notify room of leaving
this.stopGame()
+ # Destroy the room
+ console.log '-- DELETING ROOM ' + @id
+ delete Room.AllRooms[@id]
console.log '-- NUM PLAYERS IN ROOM = '+@players.length+' --'
this.startGame()
true
7 src/server/server.coffee
View
@@ -1,8 +1,9 @@
connect = require('connect')
-io = require('socket.io')
-Player = require('./player')
+io = require('socket.io')
+Player = require('./player')
+Clay = require('./encryption')
-app = connect().use(connect.static(__dirname+'/../../')).listen(8000)
+app = connect().use(connect.static(__dirname+'/../../')).listen(845)
socketServer = io.listen(app)
socketServer.configure ->
3  src/shared/constants.coffee
View
@@ -28,7 +28,8 @@ Constants =
SLIME_START_HEIGHT: 91
AI_DIFFICULTY: 0.25
MSG_FONT: 'Courier, monospace, sans-serif'
- TICK_DURATION: 16 #ms
+ FPS_RATIO: 24 / 16 # in relation to what the initial speeds were set for (16ms)
+ TICK_DURATION: 24 #ms
FRAME_DELAY: 5
STATE_SAVE: 200
SAVE_LIFETIME: 5000 # save for 2s
14 src/shared/lib/input.coffee
View
@@ -79,14 +79,16 @@ class Input
up: ['key38', 'key87']
# shortcuts for arrow states
- left: (p2) -> @keys[@shortcuts['left'][p2]] || false
- right: (p2) -> @keys[@shortcuts['right'][p2]] || false
- up: (p2) -> @keys[@shortcuts['up'][p2]] || false
+ # If we every allow playing WASD vs arrow keys we'll have to change this back to [p2] instead of [0] || [1]
+ left: (p2) -> @keys[@shortcuts['left'][0]] || @keys[@shortcuts['left'][1]] || false
+ right: (p2) -> @keys[@shortcuts['right'][0]] || @keys[@shortcuts['right'][1]] || false
+ up: (p2) -> @keys[@shortcuts['up'][0]] || @keys[@shortcuts['up'][1]] || false
reset: -> @keys[key] = false for key, val of @keys
getState: (p2) ->
- left: @keys[@shortcuts['left'][p2]]
- right: @keys[@shortcuts['right'][p2]]
- up: @keys[@shortcuts['up'][p2]]
+ # If we every allow playing WASD vs arrow keys we'll have to change this back to [p2] instead of [0] || [1]
+ left: @keys[@shortcuts['left'][0]] || @keys[@shortcuts['left'][1]] # allow left arrow or 'a' key
+ right: @keys[@shortcuts['right'][0]] || @keys[@shortcuts['right'][1]]
+ up: @keys[@shortcuts['up'][0]] || @keys[@shortcuts['up'][1]]
# setters for up, left, right
set: (shortcut, val, p2) ->
p2 ?= 0
10 src/shared/lib/sprite.coffee
View
@@ -15,10 +15,11 @@ class Sprite
@y = y
incrementPosition: (numFrames) ->
- @x += @velocity.x*numFrames
- @y += @velocity.y*numFrames
- @velocity.x += @acceleration.x * @mass * numFrames * numFrames
- @velocity.y += @acceleration.y * @mass * numFrames * numFrames
+ @x += @velocity.x * numFrames * Constants.FPS_RATIO
+ @y += @velocity.y * numFrames * Constants.FPS_RATIO
+ @velocity.x += @acceleration.x * @mass * numFrames * Constants.FPS_RATIO
+ @velocity.y += @acceleration.y * @mass * numFrames * Constants.FPS_RATIO
+
draw: (ctx, x, y) ->
x ||= @x
y ||= @y
@@ -29,6 +30,7 @@ class Sprite
y: @y
width: @width
height: @height
+
velocity:
x: @velocity.x
y: @velocity.y
59 src/shared/world.coffee
View
@@ -69,7 +69,7 @@ class GameStateBuffer
i++
findStateBefore: (clock) ->
ref = @first
- ref = ref.next while ref && ref.state && ref.state.clock >= clock
+ ref = ref.next while ref && ref.next && ref.next.state && ref.state.clock >= clock
ref
@@ -89,8 +89,8 @@ class World
@lastStep = null
@clock = 0
@numFrames = 1
- @stateSaves = new GameStateBuffer()
- @futureFrames = new GameStateBuffer()
+ #@stateSaves = new GameStateBuffer()
+ #@futureFrames = new GameStateBuffer()
# initialize game objects
@ball = new Ball(@width/4-Constants.BALL_RADIUS, @height-Constants.BALL_START_HEIGHT, Constants.BALL_RADIUS)
@p1 = new Slime(@width/4-Constants.SLIME_RADIUS, @height-Constants.SLIME_START_HEIGHT, @ball, false)
@@ -99,8 +99,12 @@ class World
@deterministic = true
reset: (servingPlayer) -> # reset positions / velocities. servingPlayer is p1 by default.
+ #@stateSaves = new GameStateBuffer()
+ #@futureFrames = new GameStateBuffer()
@p1.setPosition(@width/4-Constants.SLIME_RADIUS, @height-Constants.SLIME_START_HEIGHT)
+ @input.setState( { left: false, right: false, up: false }, 0 )
@p2.setPosition(3*@width/4-Constants.SLIME_RADIUS, @height-Constants.SLIME_START_HEIGHT)
+ @input.setState( { left: false, right: false, up: false }, 1 )
@ball.setPosition((if @p2 == servingPlayer then 3 else 1)*@width/4-Constants.BALL_RADIUS, @height-Constants.BALL_START_HEIGHT)
@pole.setPosition(@width/2-4, @height-60-64-1, 8, 64)
@p1.velocity = { x: 0, y: 0 }
@@ -139,10 +143,12 @@ class World
# precalculate the number of frames (of length TICK_DURATION) this step spans
now = new Date().getTime()
tick = Constants.TICK_DURATION
- interval ||= now - @lastStep if @lastStep && !@deterministic
+ interval ||= now - @lastStep if @lastStep # && !@deterministic
interval ||= tick # in case no interval is passed
@lastStep = now unless dontIncrementClock
+
+ ###
# automatically break up longer steps into a series of shorter steps
if interval >= tick*2
while interval > 0
@@ -154,9 +160,12 @@ class World
interval -= newInterval
return # don't continue stepping
else interval = tick
-
+ ###
+
@numFrames = interval / tick
- unless dontIncrementClock # means this is a "realtime" step, so we incrememnt the clock
+
+ ###
+ unless dontIncrementClock # means this is a "realtime" step, so we increment the clock
# look through @future frames to see if we can apply any of them now.
ref = @futureFrames.last
while ref && ref.state && ref.state.clock <= @clock
@@ -168,7 +177,9 @@ class World
ref = prevRef
@clock += interval
@stateSaves.cleanSaves(@clock)
-
+ ###
+ @clock += interval
+
this.handleInput()
@ball.incrementPosition(@numFrames)
@p1.incrementPosition(@numFrames)
@@ -181,6 +192,9 @@ class World
if @p2.y + @p2.height > @height - Constants.BOTTOM
@p2.y = @height - Constants.BOTTOM - @p2.height
@p2.velocity.y = Math.min(@p2.velocity.y, 0)
+ if @ball.y + @ball.height >= @height - Constants.BOTTOM # ball on ground
+ @ball.y = @height - Constants.BOTTOM - @ball.height
+ @ball.velocity.y = 0
# apply collisions against slimes
if @ball.y + @ball.height < @p1.y + @p1.height && Math.sqrt(Math.pow((@ball.x + @ball.radius) - (@p1.x + @p1.radius), 2) + Math.pow((@ball.y + @ball.radius) - (@p1.y + @p1.radius), 2)) < @ball.radius + @p1.radius
@@ -246,11 +260,11 @@ class World
@ball.velocity.x = .5 if Math.abs(@ball.velocity.x) < 0.1
@ball.y = @pole.y - @ball.height
- if now - @stateSaves.lastPush > Constants.STATE_SAVE # save current state every STATE_SAVE ms
- @stateSaves.lastPush = now
- @stateSaves.push # push a frame structure on to @stateSaves
- state: this.getState()
- input: null
+ #if now - @stateSaves.lastPush > Constants.STATE_SAVE # save current state every STATE_SAVE ms
+ # @stateSaves.lastPush = now
+ # @stateSaves.push # push a frame structure on to @stateSaves
+ # state: this.getState()
+ # input: null
boundsCheck: ->
@@ -263,34 +277,43 @@ class World
handleInput: ->
@p1.handleInput(@input)
@p2.handleInput(@input)
-
+
injectFrame: (frame) ->
+ # I took out this whole inserting in the past an recalculating
+ # Might be good to reimplement, it's just lagged for me
+ @setFrame frame
+ return
+ ###
# starting from that frame, recalculate input
if frame && frame.state.clock < @clock
- #console.log '============================='
- #console.log 'applying frame...'
+ console.log '============================='
+ console.log 'applying frame...'
firstFrame = @stateSaves.findStateBefore(frame.state.clock)
this.setFrame(firstFrame)
this.step(frame.state.clock - firstFrame.state.clock, true)
- #console.log 'stepped '+(frame.state.clock - firstFrame.state.clock)+'ms'
+ console.log 'c1: ' + frame.state.clock + ' c2: ' + firstFrame.state.clock
+ console.log 'stepped1 '+(frame.state.clock - firstFrame.state.clock)+'ms'
@stateSaves.push(frame) # assigns .next and .prev to frame
this.setState(frame.state)
firstIteration = true
while frame
currClock = frame.state.clock
+ console.log @clock
nextClock = if frame.prev then frame.prev.state.clock else @clock
+ console.log nextClock
this.setInput(frame.input)
unless firstIteration # this frame's state might be different,
frame.state = this.getState() # this resets the clock
frame.state.clock = currClock # fixed
firstIteration = false
this.step(nextClock - currClock, true)
- #console.log 'stepped '+(nextClock - currClock)+'ms'
+ console.log 'stepped2 '+(nextClock - currClock)+'ms'
if frame.prev then frame = frame.prev else break
else # we'll deal with this later
+ console.log 'future frame'
@futureFrames.push(frame)
-
+ ###
### -- GAME STATE GETTER + SETTERS -- ###
Please sign in to comment.
Something went wrong with that request. Please try again.