1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6declare(strict_types=1);
7
8namespace Tests\Models\Multiplayer;
9
10use App\Models\Multiplayer\PlaylistItem;
11use App\Models\Multiplayer\Room;
12use App\Models\Multiplayer\UserScoreAggregate;
13use App\Models\User;
14use App\Transformers\Multiplayer\UserScoreAggregateTransformer;
15use Tests\TestCase;
16
17class UserScoreAggregateTest extends TestCase
18{
19 private Room $room;
20
21 public function testAddingHigherScore(): void
22 {
23 $user = User::factory()->create();
24 $playlistItem = $this->createPlaylistItem();
25
26 // first play
27 $scoreLink = $this->roomAddPlay($user, $playlistItem, [
28 'accuracy' => 0.5,
29 'passed' => true,
30 'total_score' => 10,
31 ]);
32
33 $agg = UserScoreAggregate::new($user, $this->room);
34 $this->assertSame(1, $agg->completed);
35 $this->assertSame(0.5, $agg->accuracy);
36 $this->assertSame(10, $agg->total_score);
37 $this->assertSame($scoreLink->getKey(), $agg->last_score_id);
38
39 // second, higher score play
40 $scoreLink2 = $this->roomAddPlay($user, $playlistItem, [
41 'accuracy' => 1,
42 'passed' => true,
43 'total_score' => 100,
44 ]);
45
46 $agg->refresh();
47 $this->assertSame(1, $agg->completed);
48 $this->assertSame(1.0, $agg->accuracy);
49 $this->assertSame(100, $agg->total_score);
50 $this->assertSame($scoreLink2->getKey(), $agg->last_score_id);
51 }
52
53 public function testAddingLowerScore(): void
54 {
55 $user = User::factory()->create();
56 $playlistItem = $this->createPlaylistItem();
57
58 // first play
59 $scoreLink = $this->roomAddPlay($user, $playlistItem, [
60 'accuracy' => 0.5,
61 'passed' => true,
62 'total_score' => 10,
63 ]);
64
65 $agg = UserScoreAggregate::new($user, $this->room);
66 $this->assertSame(1, $agg->completed);
67 $this->assertSame(0.5, $agg->accuracy);
68 $this->assertSame(10, $agg->total_score);
69 $this->assertSame($scoreLink->getKey(), $agg->last_score_id);
70
71 // second, lower score play
72 $this->roomAddPlay($user, $playlistItem, [
73 'accuracy' => 1,
74 'passed' => true,
75 'total_score' => 1,
76 ]);
77
78 $agg->refresh();
79 $this->assertSame(1, $agg->completed);
80 $this->assertSame(0.5, $agg->accuracy);
81 $this->assertSame(10, $agg->total_score);
82 $this->assertSame($scoreLink->getKey(), $agg->last_score_id);
83 }
84
85 public function testAddingEqualScore(): void
86 {
87 $firstUser = User::factory()->create();
88 $secondUser = User::factory()->create();
89 $playlistItem = $this->createPlaylistItem();
90
91 // first user sets play
92 $firstUserPlay = $this->roomAddPlay($firstUser, $playlistItem, [
93 'accuracy' => 0.5,
94 'passed' => true,
95 'total_score' => 10,
96 ]);
97
98 $firstUserAgg = UserScoreAggregate::new($firstUser, $this->room);
99 $this->assertSame(1, $firstUserAgg->completed);
100 $this->assertSame(0.5, $firstUserAgg->accuracy);
101 $this->assertSame(10, $firstUserAgg->total_score);
102 $this->assertSame(1, $firstUserAgg->userRank());
103 $this->assertSame($firstUserPlay->getKey(), $firstUserAgg->last_score_id);
104
105 // second user sets play with same total, so they get second place due to being late
106 $secondUserPlay = $this->roomAddPlay($secondUser, $playlistItem, [
107 'accuracy' => 0.5,
108 'passed' => true,
109 'total_score' => 10,
110 ]);
111
112 $secondUserAgg = UserScoreAggregate::new($secondUser, $this->room);
113 $this->assertSame(1, $secondUserAgg->completed);
114 $this->assertSame(0.5, $secondUserAgg->accuracy);
115 $this->assertSame(10, $secondUserAgg->total_score);
116 $this->assertSame(2, $secondUserAgg->userRank());
117 $this->assertSame($secondUserPlay->getKey(), $secondUserAgg->last_score_id);
118
119 // first user sets play with same total again, but their rank should not move now
120 $this->roomAddPlay($firstUser, $playlistItem, [
121 'accuracy' => 0.5,
122 'passed' => true,
123 'total_score' => 10,
124 ]);
125
126 $firstUserAgg->refresh();
127 $this->assertSame(1, $firstUserAgg->completed);
128 $this->assertSame(0.5, $firstUserAgg->accuracy);
129 $this->assertSame(10, $firstUserAgg->total_score);
130 $this->assertSame(1, $firstUserAgg->userRank());
131 $this->assertSame($firstUserPlay->getKey(), $firstUserAgg->last_score_id);
132
133 $secondUserAgg->refresh();
134 $this->assertSame(2, $secondUserAgg->userRank());
135 }
136
137 public function testAddingMultiplePlaylistItems(): void
138 {
139 $user = User::factory()->create();
140 $playlistItem = $this->createPlaylistItem();
141 $playlistItem2 = $this->createPlaylistItem();
142
143 // first playlist item
144 $this->roomAddPlay($user, $playlistItem, [
145 'accuracy' => 0.5,
146 'passed' => true,
147 'total_score' => 10,
148 ]);
149
150 $agg = UserScoreAggregate::new($user, $this->room);
151 $this->assertSame(1, $agg->completed);
152 $this->assertSame(0.5, $agg->accuracy);
153 $this->assertSame(0.5, $agg->averageAccuracy());
154 $this->assertSame(10, $agg->total_score);
155
156 // second playlist item
157 $scoreLink = $this->roomAddPlay($user, $playlistItem2, [
158 'accuracy' => 1,
159 'passed' => true,
160 'total_score' => 100,
161 ]);
162
163 $agg->refresh();
164 $this->assertSame(2, $agg->completed);
165 $this->assertSame(1.5, $agg->accuracy);
166 $this->assertSame(0.75, $agg->averageAccuracy());
167 $this->assertSame(110, $agg->total_score);
168 $this->assertSame($scoreLink->getKey(), $agg->last_score_id);
169 }
170
171 public function testStartingPlayIncreasesAttempts(): void
172 {
173 $user = User::factory()->create();
174 $playlistItem = $this->createPlaylistItem();
175
176 static::roomStartPlay($user, $playlistItem);
177 $agg = UserScoreAggregate::new($user, $this->room);
178
179 $this->assertSame(1, $agg->attempts);
180 $this->assertSame(0, $agg->completed);
181 }
182
183 public function testFailedScoresAreAttemptsOnly(): void
184 {
185 $user = User::factory()->create();
186 $playlistItem = $this->createPlaylistItem();
187
188 $this->roomAddPlay($user, $playlistItem, [
189 'accuracy' => 0.1,
190 'passed' => false,
191 'total_score' => 10,
192 ]);
193
194 $playlistItem2 = $this->createPlaylistItem();
195 $this->roomAddPlay($user, $playlistItem2, [
196 'accuracy' => 1,
197 'passed' => true,
198 'total_score' => 1,
199 ]);
200
201 $agg = UserScoreAggregate::new($user, $this->room);
202
203 $this->assertSame(2, $agg->attempts);
204 $this->assertSame(1, $agg->completed);
205 $this->assertSame(1.0, $agg->averageAccuracy());
206 $this->assertSame(1, $agg->total_score);
207 }
208
209 public function testPassedScoresIncrementsCompletedCount(): void
210 {
211 $user = User::factory()->create();
212 $playlistItem = $this->createPlaylistItem();
213
214 $this->roomAddPlay($user, $playlistItem, [
215 'accuracy' => 1,
216 'passed' => true,
217 'total_score' => 1,
218 ]);
219
220 $agg = UserScoreAggregate::new($user, $this->room);
221
222 $this->assertSame(1, $agg->completed);
223 $this->assertSame(1, $agg->total_score);
224 }
225
226 public function testPassedScoresAreAveragedInTransformer(): void
227 {
228 $user = User::factory()->create();
229 $playlistItem = $this->createPlaylistItem();
230 $playlistItem2 = $this->createPlaylistItem();
231
232 $this->roomAddPlay($user, $playlistItem, [
233 'accuracy' => 0.1,
234 'passed' => false,
235 'total_score' => 1,
236 ]);
237
238 $this->roomAddPlay($user, $playlistItem, [
239 'accuracy' => 0.3,
240 'passed' => false,
241 'total_score' => 1,
242 ]);
243
244 $this->roomAddPlay($user, $playlistItem, [
245 'accuracy' => 0.5,
246 'passed' => true,
247 'total_score' => 1,
248 ]);
249
250 $this->roomAddPlay($user, $playlistItem2, [
251 'accuracy' => 0.8,
252 'passed' => true,
253 'total_score' => 1,
254 ]);
255
256 $agg = UserScoreAggregate::new($user, $this->room);
257
258 $result = json_item($agg, new UserScoreAggregateTransformer());
259
260 $this->assertSame(0.65, $result['accuracy']);
261 }
262
263 public function testRecalculate(): void
264 {
265 $playlistItem = $this->createPlaylistItem();
266 $user = User::factory()->create();
267 $this->roomAddPlay($user, $playlistItem, [
268 'accuracy' => 0.3,
269 'passed' => true,
270 'total_score' => 1,
271 ]);
272 $agg = UserScoreAggregate::new($user, $this->room);
273 $agg->recalculate();
274 $agg->refresh();
275
276 $this->assertSame(1, $agg->total_score);
277 $this->assertSame(1, $agg->attempts);
278 $this->assertSame(0.3, $agg->accuracy);
279 $this->assertSame(1, $agg->completed);
280 }
281
282 protected function setUp(): void
283 {
284 parent::setUp();
285
286 $this->room = Room::factory()->create();
287 }
288
289 private function createPlaylistItem(): PlaylistItem
290 {
291 return PlaylistItem::factory()->create([
292 'owner_id' => $this->room->host,
293 'room_id' => $this->room,
294 ]);
295 }
296}