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
|
|
|
|