142 lines
4.3 KiB
CoffeeScript
142 lines
4.3 KiB
CoffeeScript
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)
|