add random match-making logic
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
class Api::MatchmakingController < ApplicationController
|
||||
def create
|
||||
result = MatchmakingService.find_match(current_user.id)
|
||||
|
||||
if result
|
||||
render json: {
|
||||
match_id: result[:match_id],
|
||||
game_token: result[:game_token],
|
||||
game_server: result[:game_server],
|
||||
players: result[:players]
|
||||
}
|
||||
else
|
||||
render json: { error: "No opponent found. Try again." }, status: :request_timeout
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
MatchmakingService.cancel(current_user.id)
|
||||
render json: { message: "Matchmaking cancelled" }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,122 @@
|
||||
class MatchmakingService
|
||||
QUEUE_KEY = "matchmaking:queue"
|
||||
LOCK_KEY = "matchmaking:lock"
|
||||
MATCH_PREFIX = "matchmaking:match:"
|
||||
MATCH_TTL = 60 # seconds
|
||||
POLL_INTERVAL = 0.5 # seconds
|
||||
MAX_WAIT = 30 # seconds
|
||||
|
||||
# Game server to redirect players to (hardcoded for now)
|
||||
GAME_SERVER = ENV.fetch("GAME_SERVER_URL", "ws://game-server.example.com:8080")
|
||||
|
||||
class << self
|
||||
# Main entry point: waits for an opponent and returns match data
|
||||
def find_match(user_id)
|
||||
user_id = user_id.to_i
|
||||
|
||||
# Try to pair with an existing waiting player
|
||||
opponent_id = pop_opponent(user_id)
|
||||
|
||||
if opponent_id
|
||||
create_match(user_id, opponent_id)
|
||||
else
|
||||
# Add self to queue and wait
|
||||
add_to_queue(user_id)
|
||||
wait_for_match(user_id)
|
||||
end
|
||||
end
|
||||
|
||||
# Cancel matchmaking (remove from queue)
|
||||
def cancel(user_id)
|
||||
RedisConnection.with do |redis|
|
||||
redis.lrem(QUEUE_KEY, 0, user_id)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Atomically pop an opponent from the queue (excluding self)
|
||||
def pop_opponent(user_id)
|
||||
RedisConnection.with do |redis|
|
||||
redis.watch(QUEUE_KEY) do
|
||||
queue = redis.lrange(QUEUE_KEY, 0, -1)
|
||||
opponent = queue.find { |id| id.to_i != user_id }
|
||||
|
||||
if opponent
|
||||
result = redis.multi do |tx|
|
||||
tx.lrem(QUEUE_KEY, 0, opponent)
|
||||
end
|
||||
return opponent.to_i if result&.first&.positive?
|
||||
else
|
||||
redis.unwatch
|
||||
end
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def add_to_queue(user_id)
|
||||
RedisConnection.with do |redis|
|
||||
# Remove any stale entries first
|
||||
redis.lrem(QUEUE_KEY, 0, user_id)
|
||||
redis.rpush(QUEUE_KEY, user_id)
|
||||
end
|
||||
end
|
||||
|
||||
def wait_for_match(user_id)
|
||||
start_time = Time.current
|
||||
match_id = nil
|
||||
|
||||
while Time.current - start_time < MAX_WAIT
|
||||
RedisConnection.with do |redis|
|
||||
match_id = redis.get("#{MATCH_PREFIX}#{user_id}")
|
||||
end
|
||||
break if match_id
|
||||
|
||||
sleep POLL_INTERVAL
|
||||
end
|
||||
|
||||
if match_id
|
||||
# Retrieve and delete the match data
|
||||
data = nil
|
||||
RedisConnection.with do |redis|
|
||||
data = redis.get("#{MATCH_PREFIX}match:#{match_id}")
|
||||
redis.del("#{MATCH_PREFIX}#{user_id}")
|
||||
redis.del("#{MATCH_PREFIX}match:#{match_id}")
|
||||
end
|
||||
JSON.parse(data, symbolize_names: true) if data
|
||||
else
|
||||
# Timeout: remove from queue
|
||||
cancel(user_id)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def create_match(player1_id, player2_id)
|
||||
match_id = SecureRandom.uuid
|
||||
game_token = JwtService.encode(
|
||||
match_id: match_id,
|
||||
player1_id: player1_id,
|
||||
player2_id: player2_id,
|
||||
game_server: GAME_SERVER,
|
||||
exp: 1.hour.from_now.to_i
|
||||
)
|
||||
|
||||
match_data = {
|
||||
match_id: match_id,
|
||||
game_token: game_token,
|
||||
game_server: GAME_SERVER,
|
||||
players: [player1_id, player2_id]
|
||||
}
|
||||
|
||||
# Store match data for both players
|
||||
RedisConnection.with do |redis|
|
||||
redis.set("#{MATCH_PREFIX}match:#{match_id}", match_data.to_json, ex: MATCH_TTL)
|
||||
redis.set("#{MATCH_PREFIX}#{player1_id}", match_id, ex: MATCH_TTL)
|
||||
redis.set("#{MATCH_PREFIX}#{player2_id}", match_id, ex: MATCH_TTL)
|
||||
end
|
||||
|
||||
match_data
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user