Leaderboard finished

This commit is contained in:
Vari
2025-09-06 20:23:52 +02:00
parent 280d5457a5
commit 85ecfa5560
3 changed files with 130 additions and 114 deletions

View File

@@ -8,4 +8,27 @@ enum Faction: string
case Hunter = 'Hunter';
case Runner = 'Runner';
case Emcee = 'Emcee';
/**
* @return array<Runner|Hunter>
*/
public function getCharacterList(): array {
return match ($this) {
self::Hunter => [
Hunter::Inquisitor,
Hunter::Stalker,
Hunter::Poacher,
Hunter::Mass
],
self::Runner => [
Runner::Smoke,
Runner::Dash,
Runner::Switch,
Runner::Ghost,
Runner::Sawbones,
Runner::Ink,
],
default => [],
};
}
}

View File

@@ -6,37 +6,65 @@
use App\Enums\Game\Hunter;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Leaderboard\GetScoresRequest;
use App\Http\Responses\Api\Leaderboard\GetScoresResponse;
use App\Http\Responses\Api\Leaderboard\LeaderboardEntry;
use App\Models\Admin\Archive\ArchivedPlayerProgression;
use App\Models\User\User;
use Carbon\Carbon;
use Illuminate\Database\Query\Builder;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use stdClass;
class LeaderboardController extends Controller
{
const HUNTER_CHARACTER_LIST = [
Hunter::Inquisitor,
Hunter::Stalker,
Hunter::Poacher,
Hunter::Mass
];
public function getScores(GetScoresRequest $request)
{
$user = \Auth::user();
$response = new GetScoresResponse();
$dateFilter = Carbon::now()->startOfMonth();
$topScores = [];
$startDate = static::getCurrentLeaderboardStartDate();
dump($request->playerIds);
static::getLeaderboardTopScores(5, $request->faction, $startDate)
->each(function (ArchivedPlayerProgression $item, int $rank) use (&$topScores) {
$topScores[] = new LeaderboardEntry(
$item->user,
$item->gained_experience,
$rank + 1,
);
});
$subSubQuery = ArchivedPlayerProgression::query();
$subSubQuery->select(['user_id'])->addSelect(DB::raw('MAX(gained_experience) as gained_experience'));
$subSubQuery->whereDate('created_at', '>=', $dateFilter);
$subSubQuery->groupBy('user_id');
$subSubQuery->orderByDesc('gained_experience');
foreach ($request->playerIds as $playerId) {
$entries = static::getLeaderboardEntriesForPlayer($playerId, $request->faction, $startDate);
$response->playerScores[$playerId] = [];
$entries->each(function (ArchivedPlayerProgression $item, int $key) use (&$response, $playerId) {
$response->playerScores[$playerId][] = new LeaderboardEntry(
$item->user,
$item->gained_experience,
$item->rank,
);
});
}
$response->topScores = $topScores;
$response->leaderboardSize = static::getLeaderboardSize($request->faction, $startDate);
$response->leaderboardReset = $startDate->addMonth()->firstOfMonth();
return $response;
}
/**
* @param string $userId
* @param Faction $faction
* @param Carbon $startDate Player scores before this Time won't get selected.
* @param ?Carbon $endDate scores after this time won't be selected, defaults to now when null.
* @return Collection<int, ArchivedPlayerProgression>
*/
public static function getLeaderboardEntriesForPlayer(string $userId, Faction $faction, Carbon $startDate, ?Carbon $endDate = null): Collection
{
$endDate ??= Carbon::now();
$subSubQuery = static::getBaseSelectQuery($faction, $startDate, $endDate);
$query = ArchivedPlayerProgression::query();
$query->orderBy('gained_experience', 'desc');
@@ -45,103 +73,74 @@ public function getScores(GetScoresRequest $request)
$query->fromSub($subSubQuery, 'a');
$whereQuery = DB::query();
$whereQuery->where('user_id', $request->playerIds[0]);
$whereQuery->where('user_id', $userId);
$whereQuery->fromSub($query, 'c');
$whereQuery->selectRaw('c.rank + 1 AS "user_rank"');
$mainQuery = ArchivedPlayerProgression::query();
$mainQuery->fromSub($query, 'b');
$mainQuery->where('rank', '<=', function (Builder $whereQuery) use ($request, $query) {
$whereQuery->where('user_id', $request->playerIds[0]);
$mainQuery->where('rank', '<=', function (\Illuminate\Database\Query\Builder $whereQuery) use ($userId, $query) {
$whereQuery->where('user_id', $userId);
$whereQuery->fromSub($query, 'c');
$whereQuery->selectRaw('(c.rank + 1)');
});
$mainQuery->orderByDesc('rank');
$mainQuery->limit(5);
dd($mainQuery->toRawSql());
$username = $user->last_known_username . '_' . Str::substr($user->id, 0, 8);
return <<<JSON
{
"topScores": [
{
"id": "35c58208-2853-4ec5-9a72-be9aef984df0",
"score": 35265,
"rank": 1,
"playerName": "Clonebones_35c58208"
},
{
"id": "20c8047d-838d-4838-b8fb-64b9ed8253fe",
"score": 23355,
"rank": 2,
"playerName": "Cookiemonster_20c8047d"
},
{
"id": "0609e627-610e-42a3-8111-4ebc5a44121a",
"score": 21070,
"rank": 3,
"playerName": "Dash_0609e627"
},
{
"id": "30e5b06e-6b2c-464a-a990-d5de469fb5df",
"score": 18575,
"rank": 4,
"playerName": "Switch_30e5b06e"
},
{
"id": "08be1a3f-e4c0-4e4f-b2c3-c19ead033da0",
"score": 16730,
"rank": 5,
"playerName": "Bob_08be1a3f"
return $mainQuery->get();
}
],
"playerScores": {
"$user->id": [
{
"id": "13d86f42-8446-49b5-a0c4-077ba23a43e4",
"score": 12130,
"rank": 40,
"playerName": "Clonebones_13d86f42"
},
{
"id": "1f58fbae-23b2-481f-b679-0f1f7ab4f31a",
"score": 12120,
"rank": 41,
"playerName": "Inked_1f58fbae"
},
{
"id": "cdd7bea9-f20a-4cc0-ae82-3297614933b7",
"score": 12085,
"rank": 42,
"playerName": "Fog_cdd7bea9"
},
{
"id": "7c6c1e40-4e6c-47a7-a2a3-480f8b774994",
"score": 12085,
"rank": 43,
"playerName": "Dash_7c6c1e40"
},
{
"id": "$user->id",
"score": 12015,
"rank": 44,
"playerName": "$username"
},
{
"id": "0e38856d-97a5-4222-b546-f3f9360102b4",
"score": 12010,
"rank": 45,
"playerName": "Ghost_0e38856d"
}
]
},
"leaderboardSize": 8497,
"leaderboardReset": 1753210800
}
JSON;
/**
* @param int $count
* @param Faction $faction
* @param Carbon $startDate Player scores before this Time won't get selected.
* @param ?Carbon $endDate scores after this time won't be selected, defaults to now when null.
* @return Collection<int, ArchivedPlayerProgression>
*/
public static function getLeaderboardTopScores(int $count, Faction $faction, Carbon $startDate, ?Carbon $endDate = null): Collection
{
$endDate ??= Carbon::now();
$query = static::getBaseSelectQuery($faction, $startDate, $endDate);
$query->limit($count);
return $query->get();
}
public static function getCurrentLeaderboardStartDate(): Carbon
{
return Carbon::today()->firstOfMonth();
}
/**
* @param Faction $faction
* @param Carbon $startDate Player scores before this Time won't get selected.
* @param ?Carbon $endDate scores after this time won't be selected, defaults to now when null.
* @return int
*/
public static function getLeaderboardSize(Faction $faction, Carbon $startDate, ?Carbon $endDate = null): int
{
$endDate ??= Carbon::now();
$query = ArchivedPlayerProgression::query();
$query->whereDate('created_at', '>=', $startDate);
$query->whereDate('created_at', '<=', $endDate);
$query->whereIn('played_character', $faction->getCharacterList());
$query->groupBy('user_id');
return $query->count();
}
protected static function getBaseSelectQuery(Faction $faction, Carbon $startDate, Carbon $endDate): Builder
{
$subSubQuery = ArchivedPlayerProgression::query();
$subSubQuery->select(['user_id'])->addSelect(DB::raw('MAX(gained_experience) as gained_experience'));
$subSubQuery->whereDate('created_at', '>=', $startDate);
$subSubQuery->whereDate('created_at', '>=', $endDate);
$subSubQuery->whereIn('played_character', $faction->getCharacterList());
$subSubQuery->groupBy('user_id');
$subSubQuery->orderByDesc('gained_experience');
return $subSubQuery;
}
}

View File

@@ -6,16 +6,10 @@
class LeaderboardEntry implements \JsonSerializable
{
public User $user;
public int $score;
public int $rank;
public function __construct(
User $user,
int $score,
int $rank
public User $user,
public int $score,
public int $rank,
)
{}