|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace App\Models\Eloquent\Tool; |
|
4
|
|
|
|
|
5
|
|
|
use App\Models\Eloquent\ProblemSolution; |
|
6
|
|
|
use App\Models\Eloquent\Submission; |
|
7
|
|
|
use App\Models\Eloquent\User; |
|
8
|
|
|
use Arr; |
|
9
|
|
|
use Cache; |
|
10
|
|
|
use Carbon; |
|
11
|
|
|
use DB; |
|
12
|
|
|
|
|
13
|
|
|
class SiteRank |
|
14
|
|
|
{ |
|
15
|
|
|
private static $professionalRanking = [ |
|
16
|
|
|
"Legendary Grandmaster" => "cm-colorful-text", |
|
17
|
|
|
"International Grandmaster" => "wemd-pink-text", |
|
18
|
|
|
"Grandmaster" => "wemd-red-text", |
|
19
|
|
|
"International Master" => "wemd-amber-text", |
|
20
|
|
|
"Master" => "wemd-orange-text", |
|
21
|
|
|
"Candidate Master" => "wemd-purple-text", |
|
22
|
|
|
"Expert" => "wemd-blue-text", |
|
23
|
|
|
"Specialist" => "wemd-cyan-text", |
|
24
|
|
|
"Pupil" => "wemd-green-text", |
|
25
|
|
|
"Newbie" => "wemd-gray-text", |
|
26
|
|
|
]; |
|
27
|
|
|
|
|
28
|
|
|
private static $professionalRankingPer = [ |
|
29
|
|
|
"Legendary Grandmaster" => 3000, |
|
30
|
|
|
"International Grandmaster" => 2600, |
|
31
|
|
|
"Grandmaster" => 2400, |
|
32
|
|
|
"International Master" => 2300, |
|
33
|
|
|
"Master" => 2100, |
|
34
|
|
|
"Candidate Master" => 1900, |
|
35
|
|
|
"Expert" => 1600, |
|
36
|
|
|
"Specialist" => 1400, |
|
37
|
|
|
"Pupil" => 1200, |
|
38
|
|
|
"Newbie" => 1, |
|
39
|
|
|
]; |
|
40
|
|
|
|
|
41
|
|
|
private static $casualRanking = [ |
|
42
|
|
|
"Fleet Admiral" => "cm-colorful-text", |
|
43
|
|
|
"Admiral" => "wemd-pink-text", |
|
44
|
|
|
"Vice Admiral" => "wemd-red-text", |
|
45
|
|
|
"Captain" => "wemd-deep-orange-text", |
|
46
|
|
|
"Commander" => "wemd-orange-text", |
|
47
|
|
|
"Lieutenant Commander" => "wemd-purple-text", |
|
48
|
|
|
"Lieutenant" => "wemd-blue-text", |
|
49
|
|
|
"Ensign" => "wemd-cyan-text", |
|
50
|
|
|
"Apprentice" => "wemd-green-text", |
|
51
|
|
|
"Recruit" => "wemd-gray-text", |
|
52
|
|
|
]; |
|
53
|
|
|
|
|
54
|
|
|
private static $casualRankingPer = [ |
|
55
|
|
|
"Fleet Admiral" => 1, |
|
56
|
|
|
"Admiral" => 5, |
|
57
|
|
|
"Vice Admiral" => 10, |
|
58
|
|
|
"Captain" => 10, |
|
59
|
|
|
"Commander" => 50, |
|
60
|
|
|
"Lieutenant Commander" => 100, |
|
61
|
|
|
"Lieutenant" => 300, |
|
62
|
|
|
"Ensign" => 700, |
|
63
|
|
|
"Apprentice" => 1000, |
|
64
|
|
|
"Recruit" => 400, |
|
65
|
|
|
]; |
|
66
|
|
|
|
|
67
|
|
|
public static function getColor($rankTitle) |
|
68
|
|
|
{ |
|
69
|
|
|
if (is_null($rankTitle)) { |
|
70
|
|
|
return ""; |
|
71
|
|
|
} |
|
72
|
|
|
return self::$casualRanking[$rankTitle]; |
|
73
|
|
|
} |
|
74
|
|
|
|
|
75
|
|
|
public static function getProfessionalColor($rankTitle) |
|
76
|
|
|
{ |
|
77
|
|
|
if (is_null($rankTitle)) { |
|
78
|
|
|
return self::$professionalRanking["None"]; |
|
79
|
|
|
} |
|
80
|
|
|
return self::$professionalRanking[$rankTitle]; |
|
81
|
|
|
} |
|
82
|
|
|
|
|
83
|
|
|
public static function list($num) |
|
84
|
|
|
{ |
|
85
|
|
|
$rankList = Cache::tags(['rank'])->get('general'); |
|
86
|
|
|
if (blank($rankList)) { |
|
87
|
|
|
$rankList = []; |
|
88
|
|
|
} |
|
89
|
|
|
$rankList = collect($rankList)->slice(0, $num); |
|
90
|
|
|
$userIDArr = $rankList->pluck('uid'); |
|
91
|
|
|
$userInfoRaw = User::whereIntegerInRaw('id', $userIDArr)->get(); |
|
92
|
|
|
$userInfo = []; |
|
93
|
|
|
foreach ($userInfoRaw as $u) { |
|
94
|
|
|
$userInfo[$u->id] = $u; |
|
95
|
|
|
} |
|
96
|
|
|
return $rankList->map(function ($item) use ($userInfo) { |
|
97
|
|
|
$item["details"] = isset($userInfo[$item["uid"]]) ? $userInfo[$item["uid"]] : []; |
|
98
|
|
|
return $item; |
|
99
|
|
|
}); |
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
|
private static function getRecords(Carbon $from = null) |
|
103
|
|
|
{ |
|
104
|
|
|
$userAcceptedRecords = Submission::select("uid", DB::raw("count(distinct pid) as solved"))->where("verdict", "Accepted"); |
|
105
|
|
|
$userCommunityRecords = ProblemSolution::select("uid", DB::raw("count(distinct pid) as community"))->where("audit", 1); |
|
106
|
|
|
if(filled($from)){ |
|
107
|
|
|
$userAcceptedRecords = $userAcceptedRecords->where("submission_date", ">", $from->timestamp); |
|
108
|
|
|
$userCommunityRecords = $userCommunityRecords->where("created_at", ">", $from); |
|
109
|
|
|
} |
|
110
|
|
|
$userAcceptedRecords = collect($userAcceptedRecords->groupBy("uid")->get()->toArray()); |
|
111
|
|
|
$userCommunityRecords = collect($userCommunityRecords->groupBy("uid")->get()->toArray()); |
|
112
|
|
|
$totUserRecords = $userAcceptedRecords->pluck('uid')->merge($userCommunityRecords->pluck('uid'))->unique(); |
|
113
|
|
|
$rankList = []; |
|
114
|
|
|
foreach($totUserRecords as $uid) { |
|
115
|
|
|
$rankList[$uid]['uid'] = $uid; |
|
116
|
|
|
$rankList[$uid]['solved'] = 0; |
|
117
|
|
|
$rankList[$uid]['community'] = 0; |
|
118
|
|
|
$rankList[$uid]['tot'] = 0; |
|
119
|
|
|
} |
|
120
|
|
|
foreach($userAcceptedRecords as $userAcceptedRecord) { |
|
121
|
|
|
$rankList[$userAcceptedRecord['uid']]['solved'] = $userAcceptedRecord['solved']; |
|
122
|
|
|
} |
|
123
|
|
|
foreach($userCommunityRecords as $userCommunityRecord) { |
|
124
|
|
|
$rankList[$userCommunityRecord['uid']]['community'] = $userCommunityRecord['community']; |
|
125
|
|
|
} |
|
126
|
|
|
foreach($rankList as &$rankItem) { |
|
127
|
|
|
$rankItem['tot'] = $rankItem['solved'] + $rankItem['community']; |
|
128
|
|
|
} |
|
129
|
|
|
unset($rankItem); |
|
130
|
|
|
return $rankList; |
|
131
|
|
|
} |
|
132
|
|
|
|
|
133
|
|
|
private static function parseCoefficient($rankList) |
|
134
|
|
|
{ |
|
135
|
|
|
$activityCoefficient = self::getRecords(Carbon::parse('-1 months')); |
|
136
|
|
|
$activityCoefficientDivider = collect($activityCoefficient)->max('tot'); |
|
137
|
|
|
if(blank($activityCoefficientDivider)) { |
|
138
|
|
|
$activityCoefficientDivider = 1; |
|
139
|
|
|
} |
|
140
|
|
|
foreach ($rankList as $uid => $rankItem) { |
|
141
|
|
|
if(isset($activityCoefficient[$uid])){ |
|
142
|
|
|
$activityTot = $activityCoefficient[$uid]['tot']; |
|
143
|
|
|
} else { |
|
144
|
|
|
$activityTot = 0; |
|
145
|
|
|
} |
|
146
|
|
|
$rankList[$uid]["activityCoefficient"] = ($activityTot / $activityCoefficientDivider) + 0.5; |
|
147
|
|
|
$rankList[$uid]["points"] = $rankList[$uid]["tot"] * $rankList[$uid]["activityCoefficient"]; |
|
148
|
|
|
} |
|
149
|
|
|
usort($rankList, function($a, $b) { |
|
150
|
|
|
return $b['points'] <=> $a['points']; |
|
151
|
|
|
}); |
|
152
|
|
|
return collect($rankList); |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
public static function isTopOneHundred($rank) |
|
156
|
|
|
{ |
|
157
|
|
|
return (1 <= $rank && $rank <= 100); |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
public static function getRankString($rank) |
|
161
|
|
|
{ |
|
162
|
|
|
return filled($rank) ? "#$rank" : "unrated"; |
|
163
|
|
|
} |
|
164
|
|
|
|
|
165
|
|
|
private static function sendMessage($userID, $currentRank, $originalRank) |
|
166
|
|
|
{ |
|
167
|
|
|
if(self::isTopOneHundred($currentRank)) { |
|
168
|
|
|
$title = __('message.rank.up.title'); |
|
169
|
|
|
$level = 1; |
|
170
|
|
|
} else { |
|
171
|
|
|
$title = __('message.rank.down.title'); |
|
172
|
|
|
$level = 2; |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
return sendMessage([ |
|
176
|
|
|
'sender' => config('app.official_sender'), |
|
177
|
|
|
'receiver' => $userID, |
|
178
|
|
|
'title' => $title, |
|
179
|
|
|
'type' => 6, |
|
180
|
|
|
'level' => $level, |
|
181
|
|
|
'data' => [ |
|
182
|
|
|
'currentRank' => $currentRank, |
|
183
|
|
|
'originalRank' => $originalRank, |
|
184
|
|
|
] |
|
185
|
|
|
]); |
|
186
|
|
|
} |
|
187
|
|
|
|
|
188
|
|
|
public static function rankList() |
|
189
|
|
|
{ |
|
190
|
|
|
$originalRankList = self::list(100); |
|
191
|
|
|
Cache::tags(['rank'])->flush(); |
|
192
|
|
|
$rankList = self::getRecords(); |
|
193
|
|
|
$totUsers = count($rankList); |
|
194
|
|
|
if ($totUsers > 0) { |
|
195
|
|
|
// $rankList = DB::select("SELECT *,solvedCount+communityCount as totValue, 1 as activityCoefficient FROM (SELECT uid,sum(solvedCount) as solvedCount,sum(communityCount) as communityCount FROM ((SELECT uid,count(DISTINCT submission.pid) as solvedCount,0 as communityCount from submission where verdict=\"Accepted\" group by uid) UNION (SELECT uid,0 as solvedCount,count(DISTINCT pid) from problem_solution where audit=1 group by uid)) as temp GROUP BY uid) as temp2 ORDER BY solvedCount+communityCount DESC"); |
|
196
|
|
|
$rankList = self::parseCoefficient($rankList); |
|
197
|
|
|
$rankIter = 1; |
|
198
|
|
|
$rankValue = 1; |
|
199
|
|
|
$rankSolved = -1; |
|
200
|
|
|
$rankListCached = []; |
|
201
|
|
|
self::procRankingPer($totUsers); |
|
202
|
|
|
foreach ($rankList as $rankItem) { |
|
203
|
|
|
if ($rankSolved != $rankItem["points"]) { |
|
204
|
|
|
$rankValue = $rankIter; |
|
205
|
|
|
$rankSolved = $rankItem["points"]; |
|
206
|
|
|
} |
|
207
|
|
|
$rankTitle = self::getRankTitle($rankValue); |
|
208
|
|
|
Cache::tags(['rank', $rankItem["uid"]])->put("rank", $rankValue, 86400); |
|
209
|
|
|
Cache::tags(['rank', $rankItem["uid"]])->put("title", $rankTitle, 86400); |
|
210
|
|
|
$rankListCached[] = [ |
|
211
|
|
|
"uid" => $rankItem["uid"], |
|
212
|
|
|
"rank" => $rankValue, |
|
213
|
|
|
"title" => $rankTitle, |
|
214
|
|
|
"titleColor" => self::getColor($rankTitle), |
|
215
|
|
|
"solved" => $rankItem["solved"], |
|
216
|
|
|
"community" => $rankItem["community"], |
|
217
|
|
|
"activityCoefficient" => $rankItem["activityCoefficient"], |
|
218
|
|
|
]; |
|
219
|
|
|
$rankIter++; |
|
220
|
|
|
} |
|
221
|
|
|
Cache::tags(['rank'])->put("general", $rankListCached, 86400); |
|
222
|
|
|
$currentRankList = self::list(100); |
|
223
|
|
|
self::sendRankUpDownMessage($originalRankList, $currentRankList); |
|
224
|
|
|
} |
|
225
|
|
|
} |
|
226
|
|
|
|
|
227
|
|
|
private static function sendRankUpDownMessage($originalRankList, $currentRankList) |
|
228
|
|
|
{ |
|
229
|
|
|
if(blank($originalRankList) || blank($currentRankList)) { |
|
230
|
|
|
return; |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
$originalRankUID = []; |
|
234
|
|
|
foreach($originalRankList as $originalRankItem) { |
|
235
|
|
|
$originalRankUID[] = $originalRankItem['uid']; |
|
236
|
|
|
} |
|
237
|
|
|
|
|
238
|
|
|
$currentRankUID = []; |
|
239
|
|
|
foreach($currentRankList as $currentRankItem) { |
|
240
|
|
|
$currentRankUID[] = $currentRankItem['uid']; |
|
241
|
|
|
} |
|
242
|
|
|
|
|
243
|
|
|
foreach($originalRankList as $originalRankItem) { |
|
244
|
|
|
if(in_array($originalRankItem['uid'], $currentRankUID)) { |
|
245
|
|
|
continue; |
|
246
|
|
|
} |
|
247
|
|
|
self::sendMessage($originalRankItem['uid'], Cache::tags(['rank', $originalRankItem['uid']])->get("rank", null), $originalRankItem['rank']); |
|
248
|
|
|
} |
|
249
|
|
|
|
|
250
|
|
|
foreach($currentRankList as $currentRankItem) { |
|
251
|
|
|
if(in_array($currentRankItem['uid'], $originalRankUID)) { |
|
252
|
|
|
continue; |
|
253
|
|
|
} |
|
254
|
|
|
self::sendMessage($currentRankItem['uid'], $currentRankItem['rank'], null); |
|
255
|
|
|
} |
|
256
|
|
|
} |
|
257
|
|
|
|
|
258
|
|
|
public static function getProfessionalRanking() |
|
259
|
|
|
{ |
|
260
|
|
|
$professionalRankList = []; |
|
261
|
|
|
$verifiedUsers = User::all(); |
|
262
|
|
|
$rankIter = 0; |
|
263
|
|
|
foreach ($verifiedUsers as $user) { |
|
264
|
|
|
$rankVal = $user->professional_rate; |
|
265
|
|
|
$rankTitle = self::getProfessionalTitle($rankVal); |
|
266
|
|
|
$titleColor = self::getProfessionalColor($rankTitle); |
|
267
|
|
|
$professionalRankList[$rankIter++] = [ |
|
268
|
|
|
"name" => $user->name, |
|
269
|
|
|
"uid" => $user->id, |
|
270
|
|
|
"avatar" => $user->avatar, |
|
271
|
|
|
"professionalRate" => $user->professional_rate, |
|
272
|
|
|
"rankTitle" => $rankTitle, |
|
273
|
|
|
"titleColor" => $titleColor |
|
274
|
|
|
]; |
|
275
|
|
|
} |
|
276
|
|
|
return $professionalRankList; |
|
277
|
|
|
} |
|
278
|
|
|
|
|
279
|
|
|
private static function procRankingPer($totUsers) |
|
280
|
|
|
{ |
|
281
|
|
|
if ($totUsers > 0) { |
|
282
|
|
|
$tot = 0; |
|
283
|
|
|
$cur = 0; |
|
284
|
|
|
foreach (self::$casualRankingPer as $c) { |
|
285
|
|
|
$tot += $c; |
|
286
|
|
|
} |
|
287
|
|
|
foreach (self::$casualRankingPer as &$c) { |
|
288
|
|
|
$c = round($c * $totUsers / $tot); |
|
289
|
|
|
$cur += $c; |
|
290
|
|
|
$c = $cur; |
|
291
|
|
|
} |
|
292
|
|
|
$c = $totUsers; |
|
293
|
|
|
unset($c); |
|
294
|
|
|
} |
|
295
|
|
|
} |
|
296
|
|
|
|
|
297
|
|
|
public static function getRankTitle($rankVal) |
|
298
|
|
|
{ |
|
299
|
|
|
foreach (self::$casualRankingPer as $title => $c) { |
|
300
|
|
|
if ($rankVal <= $c) { |
|
301
|
|
|
return $title; |
|
302
|
|
|
} |
|
303
|
|
|
} |
|
304
|
|
|
return Arr::last(self::$casualRankingPer); |
|
305
|
|
|
} |
|
306
|
|
|
|
|
307
|
|
|
public static function getProfessionalTitle($rankVal) |
|
308
|
|
|
{ |
|
309
|
|
|
foreach (self::$professionalRankingPer as $title => $point) { |
|
310
|
|
|
if ($rankVal >= $point) { |
|
311
|
|
|
return $title; |
|
312
|
|
|
} |
|
313
|
|
|
} |
|
314
|
|
|
return Arr::last(self::$professionalRankingPer); |
|
315
|
|
|
} |
|
316
|
|
|
} |
|
317
|
|
|
|