ratings = require('./ratings') alts = require('./alts') INITIAL_RANGE = 100 RANGE_INCREMENT = 100 class QueuedPlayer constructor: (player) -> @player = player @range = INITIAL_RANGE @rating = null # needs to be updated by getRatings intersectsWith: (other) -> leftMin = @rating - (@range / 2) leftMax = @rating + (@range / 2) rightMin = other.rating - (other.range / 2) rightMax = other.rating + (other.range / 2) return false if leftMin > rightMax return false if leftMax < rightMin true # A queue of users waiting for a battle class @BattleQueue constructor: -> @queue = {} @newPlayers = [] @recentlyMatched = {} @length = 0 @ranked = true # Adds a player to the queue. # "name" can either be the real name, or an alt add: (playerId, name, team, ratingKey=playerId) -> return false if !playerId return false if playerId of @queue playerObject = {id: playerId, name, team, ratingKey} player = new QueuedPlayer(playerObject) @queue[playerId] = player @newPlayers.push(player) @length += 1 return true setUnranked: -> @ranked = false remove: (playerIds) -> playerIds = Array(playerIds) if playerIds not instanceof Array for playerId in playerIds if playerId of @queue delete @queue[playerId] @length -= 1 queuedPlayers: -> Object.keys(@queue) hasUserId: (playerId) -> @queue[playerId]? hasRecentlyMatched: (player1Id, player2Id) -> players = [player1Id, player2Id].sort() key = "#{players[0]}:#{players[1]}" @recentlyMatched[key]? addRecentMatch: (player1Id, player2Id) -> players = [player1Id, player2Id].sort() key = "#{players[0]}:#{players[1]}" @recentlyMatched[key] = true setTimeout((=> delete @recentlyMatched[key]), 30 * 60 * 1000) # expire in 30 minutes size: -> @length # An internal function which loads ratings for newly queued players # and removes them from the newly queued list updateNewPlayers: (next) -> if @ranked == true ratingKeys = (queued.player.ratingKey for queued in @newPlayers) return next(null) if ratingKeys.length == 0 ratings.getRatings ratingKeys, (err, returnedRatings) => if err then return next(err) ratings.setActive ratingKeys, (err) => if err then return next(err) # Update the ratings in the player objects for rating, i in returnedRatings continue unless @hasUserId(@newPlayers[i].player.id) @newPlayers[i].rating = rating # reset the new players list, we're done @newPlayers.splice(0, @newPlayers.length) next(null) else next(null) # Returns an array of pairs. Each pair is a queue object that contains # a player and team key, corresponding to the player socket and player's team. pairPlayers: (next) -> return next(null, []) if @size() == 0 @updateNewPlayers (err) => if err then return next(err, null) sortedPlayers = (queued for id, queued of @queue) if @ranked sortedPlayers.sort((a, b) -> a.rating - b.rating) alreadyMatched = (false for [0...sortedPlayers.length]) pairs = [] for leftIdx in [0...sortedPlayers.length] continue if alreadyMatched[leftIdx] for rightIdx in [(leftIdx + 1)...sortedPlayers.length] continue if alreadyMatched[rightIdx] left = sortedPlayers[leftIdx] right = sortedPlayers[rightIdx] leftPlayer = left.player rightPlayer = right.player # Continue if these two players already played continue if @hasRecentlyMatched(leftPlayer.id, rightPlayer.id) and @ranked # If the rating difference is too large break out, we have no possible match for left break unless left.intersectsWith(right) # Everything checks out, so make the pair and break out pairs.push([leftPlayer, rightPlayer]) @remove([leftPlayer.id, rightPlayer.id]) @addRecentMatch(leftPlayer.id, rightPlayer.id) if @ranked alreadyMatched[leftIdx] = alreadyMatched[rightIdx] = true break # Expand the range of all unmatched players queued.range += RANGE_INCREMENT for id, queued of @queue # Return the list of paired players next(null, pairs)